vue-test-utils | Component Test Utils for Vue | State Container library
kandi X-RAY | vue-test-utils Summary
Support
Quality
Security
License
Reuse
- Patch a create element .
- Patches the created create element .
- Create a new Vue component instance .
- Validate given options object .
- Recursively compile a component .
- Make a stub stub stub for a component
- Converts a stub object to a stub hash .
- Checks if a slot can be scroller
- Check if a selector is a dom selector
- Merge options from config .
vue-test-utils Key Features
vue-test-utils Examples and Code Snippets
// component script
import { mapActions } from 'vuex'
export default {
name: 'foo',
mounted () {
this.mount()
},
methods: {
async mount () {
await this.firstMethod()
await this.secondMethod()
await this.thirdMethod()
},
...mapActions([
'firstMethod',
'secondMethod',
'thirdMethod'
])
}
}
// test
import { shallow, createLocalVue } from 'vue-test-utils'
import Vuex from 'vuex'
import Foo from '../Foo.vue'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('Foo.vue', () => {
let actions
let store
let wrapper
beforeEach(() => {
actions = {
firstMethod: sinon.stub().resolves(),
secondMethod: sinon.stub().resolves(),
thirdMethod: sinon.stub().resolves()
}
store = new Vuex.Store({
actions
})
wrapper = shallow(Foo, { store, localVue })
})
it('calls store actions when mounted', async () => {
await expect(actions.firstMethod.called).toBe(true)
await expect(actions.secondMethod.called).toBe(true)
await expect(actions.thirdMethod.called).toBe(true)
})
})
Trending Discussions on vue-test-utils
Trending Discussions on vue-test-utils
QUESTION
When testing a base button implementation using Jest and Vue-test-utils, the tests work but I am getting the following warning:
[Vue warn]: Unknown custom element: b-button - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I am confident is because I am not including the Buefy
plugin dependencies correctly, and I don't have a lot of experience here. Here is my single file component for the base button:
{{ buttonLabel }}
And here is my testing:
import { mount } from '@vue/test-utils'
import BaseButton from '@/components/base/BaseButton'
const Component = BaseButton
const ComponentName = 'BaseButton'
const global_wrapper = mount(Component, {})
describe(ComponentName, () => {
it('should render the button', () => {
const wrapper = global_wrapper
const button = wrapper.find('[data-testid="base-button"]')
expect(button.exists()).toBeTruthy()
}),
it('should emit the click event on a click', async () => {
const wrapper = global_wrapper
console.log(wrapper.html())
const button = wrapper.find('[data-testid="base-button"]')
button.trigger('click')
const clickCalls = wrapper.emitted('click')
expect(clickCalls).toHaveLength(1)
})
})
I would appreciate help understanding the appropriate way to include the Buefy b-button
component in the test.
ANSWER
Answered 2022-Feb-05 at 05:12To include the Buefy plugin (or any other plugin), you can use something like const localVue = createLocalVue()
from vue-test-utils to create a local vue and use the Buefy plugin, localVue.use(Buefy)
as below. This localVue
can be included when mounting the wrapper.
import { createLocalVue, mount } from '@vue/test-utils'
import Component from '../Component'
import Buefy from 'buefy'
const localVue = createLocalVue()
localVue.use(Buefy)
const global_wrapper = mount(Component, {
localVue,
})
If you only have one or two components you want to use from the plugin, you can import the individual components and run localVue.use
multiple times as such:
import { createLocalVue, mount } from '@vue/test-utils'
import Component from '../Component'
import { Button, Checkbox } from 'buefy'
const localVue = createLocalVue()
localVue.use(Button)
localVue.use(Checkbox)
const global_wrapper = mount(Component, {
localVue,
})
QUESTION
I have a strange problem with testing the frontend part of my project. I use Vue components for the frontend. The project is website for teachers to set appointments for the assistent(s) so the assistant can ready everything for class.
First let me explain the structure.
I have a component which lists all appointments by date. Every date is a seperate card and all the appointments for one date are rows on that card. Each row is a specific timeslot. Appointments can be added to the list either by clicking on a button at the top of the card or by clicking the row number.
So I created three components: AppointmentsList.Vue which gets the appointments from the backend and builds a list of the cards, AppointmentsCard.Vue, which receives the date and all the appointments for that date as props, and lastly AppointmentRow.Vue which show the detailsof the appointment in a row of the table. The state, appointments, requested dates and other data, is kept in a Vuex store.
I build the project using TDD, using Jest and Vue-test-utils for writing the tests. Mocks are used to simulate the responses of the backend. Tests showing the appointments with the cards and rows works fine. But when testing the buttons I encountered a strange problem.
In the following code I show you my tests, redacted for brevity. First everything is imported and then the responses of the backend for varieous endpoints are defined. Only the appointmentResponse is important. Note that two appointments are returned.
The function createStore builds a store out of the modules. I keep all state, getters and mutations in modules. Before each test is run, I create a new store and initialize the store with the data of the responses using the mutations of the store. After each test the jest mocks are cleared and the vue-test-utils wrapper is destroyed.
*AppointmentList.spec.js*
import {mount, createLocalVue} from '@vue/test-utils'
import flushPromises from 'flush-promises'
import AppointmentsList from '../../resources/js/components/AppointmentsList.vue'
import axios from 'axios'
import Vuex from 'vuex'
import appointmentsmodule from '../../resources/js/storemodules/appointmentsModule.js'
import classhoursmodule from '../../resources/js/storemodules/classhoursModule.js'
import classroomsmodule from '../../resources/js/storemodules/classroomsModule.js'
import experimentsmodule from '../../resources/js/storemodules/experimentsModule.js'
import locationsmodule from '../../resources/js/storemodules/locationsModule.js'
import subjectsmodule from '../../resources/js/storemodules/subjectsModule.js'
import usersmodule from '../../resources/js/storemodules/usersModule.js'
import Vue from 'vue'
import { wrap } from 'lodash'
let appointmentResponse={"status":200,"lines":2,"data":[{"id":1,"subject_id":1,"owner_id":1,"group_id":1,"appointment_at":"2021-12-29T00:00:00.000000Z","classhour_id":1,"classroom_id":1,"experiment_id":null,"short_name":"magnam","description":"Sit cum quae quae quo quo consequatur.","demo":"0","toa_preferred_id":null,"location_id":1,"created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":2,"subject_id":2,"owner_id":1,"group_id":1,"appointment_at":"2021-12-28T00:00:00.000000Z","classhour_id":1,"classroom_id":1,"experiment_id":null,"short_name":"possimus","description":"Velit eos sed esse reprehenderit.","demo":"0","toa_preferred_id":null,"location_id":1,"created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let userResponse={"status":200,"lines":3,"data":[{"id":1,"code":"Est","name":"Mr. Americo Mertz I","email":"user1@hetstreek.nl","actual_location":"1","registrar":"1","email_verified_at":"2022-01-04T15:50:26.000000Z","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","department_location_id":null,"welcome_valid_until":null,"roles":[{"id":2,"name":"toa","guard_name":"web","created_at":"2022-01-04T15:50:24.000000Z","updated_at":"2022-01-04T15:50:24.000000Z","pivot":{"model_id":"1","role_id":"2","model_type":"App\\Models\\User"}},{"id":1,"name":"beheerder","guard_name":"web","created_at":"2022-01-04T15:50:22.000000Z","updated_at":"2022-01-04T15:50:22.000000Z","pivot":{"model_id":"1","role_id":"1","model_type":"App\\Models\\User"}}]},{"id":2,"code":"Est","name":"Mr. Americo Mertz I","email":"user2@hetstreek.nl","actual_location":"2","registrar":"1","email_verified_at":"2022-01-04T15:50:26.000000Z","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","department_location_id":null,"welcome_valid_until":null,"roles":[{"id":3,"name":"docent","guard_name":"web","created_at":"2022-01-04T15:50:25.000000Z","updated_at":"2022-01-04T15:50:25.000000Z","pivot":{"model_id":"2","role_id":"3","model_type":"App\\Models\\User"}}]},{"id":3,"code":"Est","name":"Mr. Americo Mertz I","email":"user3@hetstreek.nl","actual_location":"1","registrar":"1","email_verified_at":"2022-01-04T15:50:26.000000Z","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","department_location_id":"{\"department_id\":1,\"location_id\":1,\"updated_at\":\"2022-01-04T15:50:26.000000Z\",\"created_at\":\"2022-01-04T15:50:26.000000Z\",\"id\":1}","welcome_valid_until":null,"roles":[{"id":4,"name":"leerling","guard_name":"web","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","pivot":{"model_id":"3","role_id":"4","model_type":"App\\Models\\User"}}]}]}
let classhourResponse={"status":200,"lines":2,"data":[{"id":1,"name":"9","starttime":"15:22","endtime":"12:56","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":3,"name":"1","starttime":"21:47","endtime":"20:16","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let classroomResponse={"status":200,"lines":2,"data":[{"id":1,"name":"non","number":"756","in_use":"1","student_accessible":"0","teacher_accessible":"1","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":3,"name":"ut","number":"214","in_use":"1","student_accessible":"0","teacher_accessible":"1","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let experimentResponse={"status":200,"lines":2,"data":[{"id":1,"name":"nam","description":"Aliquam nihil voluptas aut vel neque.","student_selectable":"0","user_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":3,"name":"similique","description":"Exercitationem officiis excepturi aut veniam voluptatum.","student_selectable":"1","user_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let locationResponse={"status":200,"lines":2,"data":[{"id":1,"name":"Omnis.","school_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":2,"name":"Illo.","school_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let schoolResponse={"status":200,"lines":1,"data":[{"id":1,"schoolname":"Jaydon Mante","domain":"hetstreek.nl","max_locations":"1","payed_at":null,"due_date":null,"active":"1","storage_folder":"jaydonmante_hetstreeknl","created_at":"2022-01-05T14:41:49.000000Z","updated_at":"2022-01-05T14:41:49.000000Z"}]}
let subjectResponse={"status":200,"lines":2,"data":[{"id":1,"code":"SK","description":"Quis.","block_for_days":"9","color":"A3A3A3","created_at":"2022-01-05T18:54:41.000000Z","updated_at":"2022-01-05T18:54:41.000000Z"},{"id":2,"code":"re","description":"Libero.","block_for_days":"3","color":"A3A3A3","created_at":"2022-01-05T18:54:41.000000Z","updated_at":"2022-01-05T18:54:41.000000Z"}]}
function createStore(){
return {
modules:{
appointments:appointmentsmodule,
classhours:classhoursmodule,
classrooms:classroomsmodule,
experiments:experimentsmodule,
locations:locationsmodule,
subjects: subjectsmodule,
users: usersmodule
}
}
}
let wrapper
let store
beforeEach(()=>{
const newStore=createStore()
//define a new store for each test
store=new Vuex.Store(newStore)
//sets the range of dates for which the appointments need to be shown.
let startdate = new Date(2021, 11, 27)
let enddate = new Date(startdate)
enddate.setDate(startdate.getDate()+3)
//initialize the store
store.commit('setAppointmentsPeriod',{startdate,enddate})
store.commit('setSubjectFilter', [])
store.commit('storeAppointments',[])
store.commit('storeClasshours',classhourResponse.data)
store.commit('storeClassrooms',classroomResponse.data)
store.commit('storeExperiments',experimentResponse.data)
store.commit('storeLocations',locationResponse.data)
store.commit('storeSubjects',subjectResponse.data)
store.commit('storeUsers',userResponse.data)
//log to show the appointments are empty
console.log(store.state.appointments.appointments)
jest.clearAllMocks()
})
afterEach(()=>{
jest.clearAllMocks
wrapper.destroy()
})
jest.mock("axios")
const localVue =createLocalVue()
localVue.use(Vuex)
When the component AppointmentsList is mounted in the test it detects the change of period from the store, which triggers a request to the backend and loads the appointments for that period.
The first test tests the button a top of the card. It triggers the button and verifies the modal to add an appointment is opened, fills in the form, triggers the submit button and verifies that the data is send to the backend, and the store is updated so now three appointments should be in the store. This works fine.
test('add button adds appointment to list', async ()=>{
//set up the mock data.
let classhour=1
let appointmentToAdd={
id:3,
owner_id:1,
experiment_id:null,
short_name:"Schaduwpracticum",
description:"Lichtkastje met voeding, kartonnetje, scherm, liniaal",
group_id:1,
subject_id:1,
toa_preferred_id:1,
appointment_at:"2021-12-27",
classhour_id:classhour,
classroom_id:1,
demo:false,
location_id:1,
created_at:new Date(),
updated_at:new Date()
}
//mock axios responses. Get returns the appointments, post returns the added appointment.
axios.get.mockResolvedValue({status:200 , data:appointmentResponse.data})
axios.post.mockResolvedValue({status:200, data:{"status":200, "lines":1,"data":appointmentToAdd}})
//appointment is added for this date.
let checkDate=new Date(2021,11,27)
//show all two appointments
wrapper = mount(AppointmentsList, {store, localVue})
await flushPromises()
//verify two appointments in the store
expect(store.state.appointments.appointments).toHaveLength(2)
//find add button for first date
//click buttons open add/edit modal with date field prefilled
await wrapper.find('[data-cy="20211227"]').trigger('click')
const wrappedAddAppointment=wrapper.findComponent({name:'add-appointment'})
//check modal is opened with correct date
expect(wrappedAddAppointment.vm.$props.modalState).toBe(true)
expect(wrappedAddAppointment.vm.$data.appointment.appointment_at).toStrictEqual(checkDate)
//fill in fields and click submit
await wrappedAddAppointment.find('input[id="shortname"]').setValue(appointmentToAdd.short_name)
await wrappedAddAppointment.find('input[id="description"]').setValue(appointmentToAdd.desc)
await wrappedAddAppointment.find('input[id="teacher"]').setValue(1)
await wrappedAddAppointment.find('input[id="group"]').setValue(appointmentToAdd.group_id)
await wrappedAddAppointment.find('input[id="subject"]').setValue(appointmentToAdd.subject_id)
await wrappedAddAppointment.find('input[id="preferredtoa"]').setValue(appointmentToAdd.toa_preferred_id)
await wrappedAddAppointment.find('input[id="appointment_at"]').setValue(appointmentToAdd.appointment_at)
await wrappedAddAppointment.find('input[id="classhour"]').setValue(appointmentToAdd.classhour_id)
await wrappedAddAppointment.find('input[id="classroom"]').setValue(appointmentToAdd.classroom_id)
await wrappedAddAppointment.find('input[id="demo"]').setChecked(false)
await wrappedAddAppointment.find('button[name="save-button"]').trigger('click')
//check axios post(/appointments) is called
expect(axios.post).toHaveBeenCalledTimes(1)
expect(axios.post.mock.calls[0][0]).toContain('/appointments')
expect(axios.get).toHaveBeenCalledTimes(2)
//verify the modal is closed
expect(wrappedAddAppointment.vm.$props.modalState).toBe(false)
//verify the appointment is added to the store
expect(store.state.appointments.appointments).toHaveLength(3)
//verify added appointment is in list
expect(wrapper.text()).toContain(appointmentToAdd.short_name)
})
The next test tests clicking the row number. It should also open the AddAppointment modal with date and classhour (=row number) prefilled. As adding the appointment is already tested, the test stops.
test('clicking classhour adds appointment to list', async ()=>{
//setup ajax responses
axios.get.mockResolvedValue({status:200 , data:appointmentResponse.data})
axios.post.mockResolvedValue({status:200, data:{"status":200, "lines":1,"data":appointmentToAdd}})
let checkDate=new Date(2021,11,27)
console.log(store.state.appointments.appointments)
//show all appointments
const wrapper = mount(AppointmentsList, {store, localVue})
await flushPromises()
expect(axios.get).toHaveBeenCalledTimes(1)
--> expect(store.state.appointments.appointments).toHaveLength(2)
//test fails on line above. Added next two lines to check the appointments in the store and the return value of the axios call.
console.log(store.state.appointments.appointments) //shows three appointments with the last one being the appointment added in the previous test
console.log(axios.get.mock.results[0].value) //shows only two appointments returned as expected
//find classhour button of date 27-dec-2021 and classhour 3
await wrapper.find('[data-cy="20211227classhour3"]').trigger('click')
const wrappedAddAppointment=wrapper.findComponent({name:'add-appointment'})
//check modal is opened with correct date and classhour
expect(wrappedAddAppointment.vm.$props.modalState).toBe(true)
expect(wrappedAddAppointment.vm.$data.appointment.appointment_at).toStrictEqual(checkDate)
expect(wrappedAddAppointment.vm.$data.appointment.classhour.id).toBe(3)
})
This test fails at the line marked with an arrow. Jest reports not two appointments but three in the store. As I have rebuild the store between tests, this make no sense to me. The added appointment should no longer be in the store. The mock call clearly shows two appointments in the response. But it gets stranger even more. Instead of adding the two initial appointments to the store I decided to change the response to return no appointments. I changed the mockresponse to:
axios.get.mockResolvedValue({status:200 , data:{status:200, lines:0, data:[]} })
and the verification to
expect(store.state.appointments.appointments).toHaveLength(0)
This test passes, so the added appointment from the first test is not retained. Can anyone help me shed some light on this?
Edit. Added the appointmentModule as requested in the comments.
const appointmentsmodule = {
state(){
return{
appointments:[],
appointmentPeriod:{
startdate:null,
enddate:null
},
subjectFilter:[],
}
},
mutations:{
storeAppointments(state, appointments){
//console.log('storeAppointments', appointments)
state.appointments = appointments
},
setAppointmentsPeriod(state, period){
//console.log('setAppointmentsPeriod', period)
state.appointmentPeriod = period
},
setSubjectFilter(state, filter){
//console.log('set Filter', filter)
state.subjectFilter= filter
},
addAppointment(state, appointmentToAdd){
state.appointments.push(appointmentToAdd)
}
},
getters:{
getFilteredAppointments: state=>{
//console.log('getting filtered appointments')
if(state.subjectFilter.length>0){
////console.log('filtered')
return state.appointments.filter(appointment=>{
/* //console.log(state.subjectFilter)
//console.log(appointment.subject_id)
//console.log(state.subjectFilter.includes(appointment.subject_id)) */
return state.subjectFilter.includes(appointment.subject_id)})
}
else{
////console.log('no filter')
return state.appointments
}
},
getAppointmentsPeriod(state){
//console.log('getting appointments period')
return state.appointmentPeriod
}
}
}
export default appointmentsmodule
ANSWER
Answered 2022-Feb-04 at 18:17I solved the problem. When storing the response from the backend I replaced the array in the vuex.state with the new array, thereby breaking reactivity. When I use
state.appointments.splice(0,Infinity, ...newAppointments)
all the elements of the original array are replaced with the new elements. Reactivity is preserved and the tests passes
QUESTION
I have the following lifecycle hook in my component:
async mounted () {
await this.fetchTabData()
},
said method calls a decoupled method as the data it requests can be refreshed based on user activity at runtime (ie. switching between "tabs" that each call async data)
In order to get test coverage for the above I wrote the following:
describe('mounted', () => {
test('test', async () => {
const fetchTabData = jest.fn()
wrapper = await shallowMount(Overview, {
store: new Vuex.Store({ ... }),
...
methods: { fetchTabData }
})
expect(fetchTabData).toHaveBeenCalled()
})
})
VTU tells me
[vue-test-utils]: overwriting methods via the
methods
property is deprecated and will be removed in the next major version. There is no clear migration path for themethods
property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
what is the proposed solution therefore (if there is one/are any), when the given complex method is itself a lifecycle hook?
ANSWER
Answered 2022-Jan-29 at 23:00the below passes my test and the warning no longer appears
[Edit: to clarify, Overview
is the component to be tested]
describe('mounted', () => {
test('test', async () => {
const fetchTabData = jest.fn()
Overview.methods.fetchTabData = fetchTabData
wrapper = await shallowMount(Overview, {
store: new Vuex.Store({ ... }),
...
})
expect(fetchTabData).toHaveBeenCalled()
})
})
the same approach should, in theory, work for other instances where the lifecycle hook is calling methods.
QUESTION
I am testing a VueJS 2 application using vue-test-utils library and I want to check if the mixin was passed to specific component, something like mounting the component using mount
and accessing the mixin throw something like wrapper.vm.mixins
.
I've already tried to access using: wrapper.vm.mixin
, wrapper.vm.mixins
, wrapper.mixin
, wrapper.mixins
, wrapper.vm.$mixin
and wrapper.vm.$mixins
.
My vue component is like this:
export default (Vue as VueConstructor>).extend({
name: 'MyComponent',
mixins: [MyMixin]
})
ANSWER
Answered 2021-Dec-28 at 12:14Mixin.ts
import Vue from 'vue';
const Sum = Vue.extend({
name: 'Sum',
methods: {
sum(numA: number, numB: number) {
return numA + numB;
}
}
});
export { Sum };
Answer 1:
You can import the mixin in your test and check if it was passed in wrapper.vm.$options.mixins array like this:
import { Sum } from './Sum'
const mixins = wrapper.vm.$options.mixins as any;
expect(mixins).toContain(Sum)
Answer 2:
You can test by the mixins names, but you need to be sure the mixin have a name property or it will return undefined:
const mixins = (wrapper.vm.$options.mixins as any).map((mixin: any) => mixin.options.name)
expect(mixins).toContain('Sum');
QUESTION
In a .vue file with Composition API (and Vue 3), set up the router:
const router = useRouter()
Mount the .vue file in a jest test:
const wrapper = mount(Lookup)
On execution, produces:
console.warn
[Vue warn]: injection "Symbol([vue-router]: router)" not found.
at
at
Mocking it results in the same output:
useRouter().push = jest.fn()
Setting provide results in same output:
import { useRouter } from 'vue-router'
...
const wrapper = mount(Lookup, {
global: {
plugins: [useRouter],
provide: {
router: {},
},
},
})
ANSWER
Answered 2021-Aug-13 at 14:27This solution allows me to mock useRouter()
in Jest. Note that useRouter()
is the only way to use vue-router
for composition API because this
is unavailable:
const routerPushMock = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: routerPushMock,
}),
}));
test('...', async () => {
const wrapper = mount(vueFile)
...
QUESTION
I cannot find an answer to this question anywhere.
I went through the official Nuxt documentation and through the existing Stack Overflow and Github issue discussions.
Implementation of the AuthModule:
@Module({
stateFactory: true,
namespaced: true,
})
export default class AuthModule extends VuexModule {
userData?: UserData | undefined = undefined;
prevRouteList: Routes[] = [];
error?: services.ICognitoError | undefined = undefined;
isLoading = false;
...
@VuexMutation
setIsLoading(isLoading: boolean) {
this.isLoading = isLoading;
}
...
@VuexAction({ rawError: true })
async register(registerData: { email: string; password: string }): Promise {
this.context.commit('setIsLoading', true);
this.context.commit('setError', undefined);
this.context.commit('setInitiateRegistration', false);
this.context.dispatch('setEmail', registerData.email);
try {
const { user } = await services.register(registerData.email, registerData.password);
if (user) {
this.context.dispatch('pushPrevRoute', Routes.emailVerification);
this.context.commit('setInitiateRegistration', true);
}
} catch (error: any) {
this.context.commit('setError', error);
this.context.commit('setInitiateRegistration', false);
}
this.context.commit('setIsLoading', false);
}
...
@MutationAction
setEmail(email: string) { ... }
...
get getEmail() {
return this.email;
}
...
}
My /store
directory contains only Vuex modules (like the example AuthModule). There is no index.ts where I declare and instantiate the store. Also the modules are not dynamic.
So my questions are:
What is the correct pattern of writing unit tests for Nuxt Vuex modules, defined with vuex-module-decorators synax, using Jest and vue-test-utils?
How can I unit test VuexMutations, VuexActions, MutationActions, getters etc.?
I tried instantiating the AuthModule class inside the test file, but I can't get it to work.
describe('AuthModule', () => {
const authModule = new AuthModule({...});
it('test', () => {
console.log(authModule);
/*
AuthModule {
actions: undefined,
mutations: undefined,
state: undefined,
getters: undefined,
namespaced: undefined,
modules: undefined,
userData: undefined,
prevRouteList: [],
error: undefined,
isLoading: false,
registrationInitiated: false,
registrationConfirmed: false,
forgotPasswordEmailSent: false,
forgottenPasswordReset: false,
email: '',
maskedEmail: ''
}*/
});
I also tried the approach explained here:
https://medium.com/@brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
and here:
Here's my setup based on the recommendations in those articles / links:
// jest.config.js
module.exports = {
setupFilesAfterEnv: ['/jest.setup.js'],
roots: [
'/components',
'/pages',
'/middleware',
'/layouts',
'/services',
'/store',
'/utils',
],
reporters: ['default', 'jest-sonar'],
moduleNameMapper: {
'^@/(.*)$': '/$1',
'^~/(.*)$': '/$1',
'^vue$': 'vue/dist/vue.common.js',
},
moduleFileExtensions: ['ts', 'js', 'vue', 'json'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.ts$': 'ts-jest',
'.*\\.(vue)$': 'vue-jest',
'^.+\\.(js|jsx)$': 'babel-jest-amcharts',
},
collectCoverage: true,
collectCoverageFrom: [
'/components/**/*.vue',
'/pages/**/*.vue',
'/layouts/**/*.vue',
'/middleware/**/*.ts',
'/store/**/*.ts',
'/mixins/**/*.ts',
'/services/**/*.ts',
],
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\](?!(@amcharts)\\/).+\\.(js|jsx|ts|tsx)$'],
forceExit: !!process.env.CI,
};
// jest.setup.js
import { config } from '@vue/test-utils';
import { Nuxt, Builder } from 'nuxt';
import TsBuilder from '@nuxt/typescript-build';
import nuxtConfig from './nuxt.config';
config.stubs.nuxt = { template: '' };
config.stubs['nuxt-link'] = { template: '' };
config.mocks.$t = (msg) => msg;
const nuxtResetConfig = {
loading: false,
loadingIndicator: false,
fetch: {
client: false,
server: false,
},
features: {
store: true,
layouts: false,
meta: false,
middleware: false,
transitions: false,
deprecations: false,
validate: false,
asyncData: false,
fetch: false,
clientOnline: false,
clientPrefetch: false,
clientUseUrl: false,
componentAliases: false,
componentClientOnly: false,
},
build: {
indicator: false,
terser: false,
},
};
const nuxtBuildConfig = {
...nuxtConfig,
...nuxtResetConfig,
dev: false,
extensions: ['ts'],
ssr: false,
srcDir: nuxtConfig.srcDir,
ignore: ['**/components/**/*', '**/layouts/**/*', '**/pages/**/*'],
};
const buildNuxt = async () => {
const nuxt = new Nuxt(nuxtBuildConfig);
await nuxt.moduleContainer.addModule(TsBuilder);
try {
await new Builder(nuxt).build();
return nuxt;
} catch (error) {
console.log(error);
process.exit(1);
}
};
module.exports = async () => {
const nuxt = await buildNuxt();
process.env.buildDir = nuxt.options.buildDir;
};
// jest.utils.js
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import VueFormulate from '@braid/vue-formulate';
import { mount, createLocalVue } from '@vue/test-utils';
const createStore = (storeOptions = {}) => new Vuex.Store({ ...storeOptions });
const createRouter = () => new VueRouter({});
const setup = (storeOptions) => {
const localVue = createLocalVue();
localVue.use(VueRouter);
localVue.use(Vuex);
localVue.use(VueFormulate);
const store = createStore(storeOptions);
const router = createRouter();
return { store, router, localVue };
};
export const createNuxtStore = async () => {
const storePath = `${process.env.buildDir}/store.js`;
// console.log(storePath);
const NuxtStoreFactory = await import(storePath);
const nuxtStore = await NuxtStoreFactory.createStore();
return { nuxtStore };
};
export const createTestBed =
(component, componentOptions = {}, storeOptions = {}) =>
(renderer = mount) => {
const { localVue, store, router } = setup(storeOptions);
return renderer(component, {
store,
router,
localVue,
...componentOptions,
});
};
// auth.spec.js
import { createNuxtStore } from '@/jest.utils';
describe('AuthModule', () => {
let store: any;
beforeAll(() => {
store = createNuxtStore();
});
it('should create', () => {
console.log(store);
});
});
After I run this I get this error in the console:
RUNS store/auth.spec.ts
node:internal/process/promises:245
triggerUncaughtException(err, true /* fromPromise */);
^
ModuleNotFoundError: Cannot find module 'undefined/store.js' from 'jest.utils.js'
at Resolver.resolveModule (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:306:11)
at Resolver._getVirtualMockPath (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:445:14)
at Resolver._getAbsolutePath (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:431:14)
at Resolver.getModuleID (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:404:31)
at Runtime._shouldMock (/Users/ivan.spoljaric/Documents/.../node_modules/jest-runtime/build/index.js:1521:37)
at Runtime.requireModuleOrMock (/Users/ivan.spoljaric/Documents/.../node_modules/jest-runtime/build/index.js:916:16)
at /Users/ivan.spoljaric/Documents/.../jest.utils.js:24:28
at processTicksAndRejections (node:internal/process/task_queues:94:5)
at Object.createNuxtStore (/Users/ivan.spoljaric/Documents/.../jest.utils.js:24:28) {
code: 'MODULE_NOT_FOUND',
hint: '',
requireStack: undefined,
siblingWithSimilarExtensionFound: false,
moduleName: 'undefined/store.js',
_originalMessage: "Cannot find module 'undefined/store.js' from 'jest.utils.js'"
ANSWER
Answered 2021-Sep-28 at 20:00After some trial and error I finally discovered the answer to my question.
If you are like me; only starting your journey with Vue, Nuxt & vuex-module-decorators and you get stuck tackling this exact same problem, I hope this little solo QA ping-pong finds you well!
My solution looks like this:
// auth.spec.ts
import Vuex, { Store } from 'vuex';
import { createLocalVue } from '@vue/test-utils';
import AuthModule, { IState } from './auth';
jest.mock('@/services');
const localVue = createLocalVue();
localVue.use(Vuex);
const storeOptions = {
modules: {
auth: AuthModule,
},
};
const createStore = (storeOptions: any = {}): Store<{ auth: IState }> => new Vuex.Store({ ...storeOptions });
describe('AuthModule', () => {
let store: Store<{ auth: IState }>;
beforeEach(() => {
store = createStore(storeOptions);
});
describe('mutations', () => {
// ...
it('auth/setIsLoading', () => {
expect(store.state.auth.isLoading).toBe(false);
store.commit('auth/setIsLoading', true);
expect(store.state.auth.isLoading).toBe(true);
});
// ...
});
describe('actions', () => {
// ...
it('register success', async () => {
const registerData = {
email: 'dummy@email.com',
password: 'dummy',
};
expect(store.state.auth.registrationInitiated).toBe(false);
try {
await store.dispatch('auth/register', registerData);
expect(store.state.auth.registrationInitiated).toBe(true);
} catch (error) {}
});
// ...
});
describe('mutation-actions', () => {
// ...
it('setEmail', async () => {
const dummyEmail = 'dummy@email.com';
expect(store.state.auth.email).toBe('');
await store.dispatch('auth/setEmail', dummyEmail);
expect(store.state.auth.email).toBe(dummyEmail);
});
// ...
});
describe('getters', () => {
// ...
it('auth/getError', () => {
expect(store.state.auth.error).toBe(undefined);
expect(store.getters['auth/getError']).toBe(undefined);
(store.state.auth.error as any) = 'Demmo error';
expect(store.getters['auth/getError']).toBe('Demmo error');
});
// ...
});
});
// services/auth
export async function register(email: string, password: string, attr: any = {}): Promise {
try {
return await Auth.signUp({
username: email,
password,
attributes: {
...attr,
},
});
} catch (err: any) {
return Promise.reject(createError(err, 'register'));
}
}
// createError is just a util method for formatting the error message and wiring to the correct i18n label
// services/__mock__/auth
import { createError } from '../auth';
export const register = (registerData: { email: string; password: string }) => {
try {
if (!registerData) {
throw new Error('dummy error');
}
return new Promise((resolve) => resolve({ response: { user: registerData.email } }));
} catch (err) {
return Promise.reject(createError(err, 'register'));
}
};
//
The most important thing to realise is that the vuex-module-decorators class-based module behaves just like a vue-class-component under the hood.
All of the vuex-module-decorators stuff is just syntactic sugar - a wrapper around the vue-class-component API.
To quote the docs:
In your store, you use the MyModule class itself as a module...The way we use the MyModule class is different from classical object-oriented programming and similar to how vue-class-component works. We use the class itself as module, not an object constructed by the class
Another thing to keep in mind is to use createLocalVue, which enables us to use Vue classes, plugins, components etc. without polluting the global Vue class.
Adding the Vuex plugin to createLocalVue
:
localVue.use(Vuex);
The AuthModule class is declared as a Vuex (namespaced) module inside the Vuex.Store constructor (as per docs).
const storeOptions = {
modules: {
auth: AuthModule,
},
};
const createStore = (storeOptions: any = {}): Store<{ auth: IState }> => new Vuex.Store({ ...storeOptions });
In the implementation above, AuthModule (incl. store, actions, mutations, getters...) is re-created for every test case with the help of the beforeEach
hook (so we have a clean store for every test)
The rest is pretty straightforward. You can see how I tested each part of the AuthModule (actions, mutations, getters..)
QUESTION
According to the docs an element of a wrapper can be found by name.
Using a find option object, Vue Test Utils allows for selecting elements by a name of component on wrapper components.
and the example given is
const buttonWrapper = wrapper.find({ name: 'my-button' })
buttonWrapper.trigger('click')
In my vue component I have three buttons. I want to test function of all three buttons, but I don't want to use wrapper.findAll("button").at(1)
to select the second button, because when the order of the buttons is changed, the test fails. So I decided to name each button and find them by name using the option object.
In my component I have:
Opslaan
In my test I have:
const button = wrappedAddSubject.find({name:"save-button"})
This returns undefined
instead of the button. What am I doing wrong?
ANSWER
Answered 2021-Sep-12 at 11:09I think "name" refers to the name of the component, not the attribute "name" of html element.
For ex, if you have a component:
Vue.component('save-button', {
template: "Click
})
you can find this component by its name using your syntax.
QUESTION
Since the vue-test-utils don't provide "localvue" for Vue 3 I thought of using createApp from Vue itself.
Now I am also using Typescript.
Somehow my Linter didn't care about this statement:
const app = createApp(App);
But when I want to split declaration and intantiation for my jest tests like this:
let app;
describe("Test Vue", () => {
beforeEach(() => {
app = createApp(App);
});
});
It obviously throws this Error:
Variable 'app' implicitly has type 'any' in some locations where its type cannot be determined.ts(7034)
Now I thought I am smart an just take the type that createApp says it returns:
export declare const createApp: CreateAppFunction;
like this:
let app: CreateAppFunction;
describe("Test Vue", () => {
beforeEach(() => {
app = createApp(App); //***declared Module. See End for declaration
});
});
But this will return:
Type 'App' is not assignable to type 'CreateAppFunction'.
Type 'App' provides no match for the signature '(rootComponent: Component, rootProps?: Data | null | undefined): App'.ts(2322)
So I am left wondering: What is the type of app?
***Declaration of App
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
ANSWER
Answered 2021-Aug-24 at 15:38It turns out it's this:
import { createApp, App } from "vue";
import Application from "@/App.vue";
let app: App;
describe("Test Vue", () => {
beforeEach(() => {
app = createApp(Application);
});
So the type of app that is created by createApp is import { App } from "vue";
QUESTION
I can't get vue testing to work with vue-test-utils and jest. I created a clean new project with vue cli and added jest as follows, maybe someone can follow along and tell me what I'm doing wrong. (I'm following this installation guide: https://vue-test-utils.vuejs.org/installation/#semantic-versioning)
vue create jest-test
1.1.
npm install
npm install --save-dev jest @vue/test-utils vue-jest
Added jest config to package.json:
{
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
".*\\.(vue)$": "vue-jest"
}
}
}
npm install --save-dev babel-jest @babel/core @babel/preset-env babel-core@^7.0.0-bridge.0
Adjusted jest config to:
{
"jest": {
"transform": {
// process `*.js` files with `babel-jest`
".*\\.(js)$": "babel-jest" //<-- changed this
}
}
}
- Adjusted babel config to:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
'@babel/preset-env' //<-- added this
]
};
Created example.test.js in a tests directory under the project root (jest-test/tests)
Added the following to this file:
import { mount } from '@vue/test-utils'
import HelloWorld from "@/components/HelloWorld";
test('displays message', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.text()).toContain('Welcome to Your Vue.js App')
})
Added the following to the package.json scripts: "jest": "jest"
npm run jest
Get the following error:
C:\Users\xxx\jest-test\tests\example.test.js:1
import { mount } from '@vue/test-utils'
^^^^^^
SyntaxError: Cannot use import statement outside a module
Same happens with Mocha or if I try it in an existing project. Is this a bug? I can't get it working, no matter what I do.
Edit: If I do it with Vue CLI, it works https://vue-test-utils.vuejs.org/installation/#installation-with-vue-cli-recommended
ANSWER
Answered 2021-Aug-03 at 18:40You need to transform both *.vue
files and *.js
files. I tried your setup and could reproduce the issue. But after altering jest.config.js
to the following, the tests will run fine:
module.exports = {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
transform: {
'.*\\.js$':'babel-jest',
".*\\.(vue)$": "vue-jest"
},
moduleNameMapper: {
"@/(.*)": "/src/$1",
},
testEnvironment: 'jsdom'
}
QUESTION
Hi I'm testing vue project using vue-test-utils.
What I wanna do is to test router (my project is using VueRouter).
I know that if I import routes from router file or VueRouter and use it to localVue, $route and $router properties are read-only so I can't mock it.
But when I tried like
transfers.test.js
import { createLocalVue, shallowMount } from '@vue/test-utils'
// import VueRouter from 'vue-router'
// import routes from '../../router/Routes'
import ElementUI from 'element-ui'
...
const localVue = createLocalVue()
// localVue.use(VueRouter)
localVue.use(ElementUI)
localVue.use(Vuex)
localVue.component('DefaultLayout', DefaultLayout)
// const router = new VueRouter({ routes })
...
describe('Elements', () => {
const div = document.createElement('div')
div.id = 'root'
document.body.appendChild(div)
const wrapper = shallowMount(Transfers, {
localVue,
store,
mocks: {
$route: {
params: { processing: 'failed' }
}
},
// router,
attachTo: '#root'
})
afterAll(() => {
wrapper.destroy()
})
...
console.error
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
appears.
Actually, what I truly wanna do is not to mock route but to use real router but there is another issue..
'Route with name 'something' does not exist' vue-router console.warn when using vue-test-utils
If you know the solutions for these issues, please let me know! Thank you for in advance.
ANSWER
Answered 2021-Jul-14 at 03:07I certainly solved this problem with the first answer of this question!
It seems like using VueRouter in anywhere(even in non-test codes) affects mocking $route.
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter)
}
I solved with this code in the first answer of question linked. Use this code to where VueRouter is used!
Community Discussions, Code Snippets contain sources that include Stack Exchange Network
Vulnerabilities
No vulnerabilities reported
Install vue-test-utils
Support
Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from over 650 million Knowledge Items
Find more librariesExplore Kits - Develop, implement, customize Projects, Custom Functions and Applications with kandi kits
Save this library and start creating your kit
Share this Page