I am learning how to test vue components using this combination of technologies, Vue, testing-library and vitest. My component is using vee-validate
TheLogin.vue
<template>
<div class="container mx-auto px-4 h-full">
<div class="flex content-center items-center justify-center h-full">
<div class="w-full lg:w-4/12 px-4">
<VeeForm
v-slot="{ handleSubmit, errors, isSubmitting }"
:validation-schema="schema"
as="div"
>
<form @submit="handleSubmit($event, onSubmit)" method="post">
<div
class="relative flex flex-col min-w-0 break-words w-full mb-6 shadow-lg rounded-lg bg-gray-300 border-0"
>
<div class="rounded-t mb-0 px-6 py-6">
<div class="text-center mb-3">
<h6 class="text-gray-600 text-sm font-bold">Sign in with</h6>
</div>
</div>
<div class="flex-auto px-4 lg:px-10 py-10 pt-0">
<div v-if="loginError" class="text-red-500">{{loginError}}</div>
<div class="text-gray-500 text-center mb-3 font-bold">
<small>Or sign in with credentials</small>
</div>
<div class="relative w-full mb-3">
<label
class="block uppercase text-gray-700 text-xs font-bold mb-2"
for="email"
>Email</label
>
<Field
id="email"
name="email"
placeholder="email"
class="border-0 px-3 py-3 placeholder-gray-400 text-gray-700 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full"
style="transition: all 0.15s ease 0s"
:disabled="isSubmitting"
:class="{ 'border-red-500': errors.email }"
/>
<ErrorMessage class="text-red-500 text-xs" name="email" />
</div>
<div class="relative w-full mb-3">
<label
class="block uppercase text-gray-700 text-xs font-bold mb-2"
for="password"
>Password</label
>
<Field
id="password"
name="password"
type="password"
class="border-0 px-3 py-3 placeholder-gray-400 text-gray-700 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full"
placeholder="Password"
style="transition: all 0.15s ease 0s"
:disabled="isSubmitting"
:class="{ 'border-red-500': errors.password }"
/>
<ErrorMessage
class="text-red-500 text-xs"
name="password"
/>
</div>
<div>
<label class="inline-flex items-center cursor-pointer"
><input
id="customCheckLogin"
type="checkbox"
class="form-checkbox border-0 rounded text-gray-800 ml-1 w-5 h-5"
style="transition: all 0.15s ease 0s"
/><span class="ml-2 text-sm font-semibold text-gray-700"
>Remember me</span
></label
>
</div>
<div class="text-center mt-6">
<button
class="bg-gray-900 text-white active:bg-gray-700 text-sm font-bold uppercase px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 w-full"
type="submit"
style="transition: all 0.15s ease 0s"
:disabled="isSubmitting"
v-text="isSubmitting ? 'Processing' : 'Sign In'"
></button>
</div>
</div>
</div>
</form>
</VeeForm>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import axios from "axios";
import { ErrorMessage, Form as VeeForm, Field } from "vee-validate";
import { ref } from "vue";
import * as yup from "yup"
import { useRouter } from 'vue-router'
const loading = ref(false)
const loginError =ref('')
const router = useRouter()
const schema = yup.object({
email: yup.string().required().min(8).label("Email"),
password: yup.string().required().min(8).label("Password")
});
const onSubmit = async (values, actions) => {
loading.value = true;
const formData = {
email: values.email,
password: values.password
};
axios
.post("backend-url", formData)
.then((response) => {
loading.value = false;
router.push({ name: "products.index" });
})
.catch((error) => {
actions.setErrors(error.response.data.errors);
loginError.value = error.message;
});
};
</script>
And this is my test:
import { describe, it, expect, beforeEach, test } from 'vitest'
import TheLogin from '@/Pages/TheLogin.vue'
import flushPromises from 'flush-promises';
import waitForExpect from 'wait-for-expect';
import {fireEvent, render, screen} from '@testing-library/vue'
describe('TheLogin', () => {
test('renders error message if email is empty', async () => {
const {getAllByRole, getByRole, findByRole } = render(TheLogin);
const button = getByRole('button', { name: /sign in/i })
await fireEvent.click(button)
await flushPromises()
await waitForExpect(() => {
const errorElement = getAllByRole('alert')
//this is better because the error message could change
expect(errorElement[0].textContent).toBeTruthy()
//this also works
//getByText('Email is a required field')
})
})
test('renders error message if password is empty', async () => {
const {getAllByRole, getByPlaceholderText, getByRole } = render(TheLogin)
const button = getByRole('button', { name: /sign in/i })
const emailInput = getByPlaceholderText(/email/i)
await fireEvent.update(emailInput, 'test@gmail.com')
await fireEvent.click(button)
await flushPromises()
await waitForExpect(() => {
const errorElement = getAllByRole('alert')
expect(errorElement[0].textContent).toBeTruthy()
})
})
})
When I delete 1 test, it works fine, but when there are 2 o more tests it fails, the message says this:
TestingLibraryElementError: Found multiple elements with the role "button" and name
/sign in/i
The strange thing is that it looks like it is rendering the component twice, that's why I would like to know how to reset each test, I mean run each test in isolation, thanks.
Using:
test: {
globals: true,
},
in my config was not working because I needed to change this to import defineConfig from vitest/config instead of vite
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
test: {
globals: true,
},
})
Now it is working