Testing a trigger click on a button does not work in Vue using Jest - unit-testing

Testing a trigger click on a button does not work in Vue using Jest.
When I try to find the button in the wrapper the test passes, but when I try a trigger click on the same button so a method will be called it does not work.
Here is the vue file snapshot of the button:
<v-btn #click="viewAppointment(appointment)" class="ma-2" dark small color="orange" id="view-appointment" data-viewAppointmentBtn>
<v-icon left>mdi-eye</v-icon>
<span>View</span>
</v-btn>
Here is the js file that contains the simple method call::
viewAppointment(appointment) {
this.appointment = appointment;
this.viewAppointmentDialog = !this.viewAppointmentDialog;
},
Here is the .spec.js file for the test::
import './setup.js';
import CoachAppointmentsRequests from '../dashboard/coach/appointments/requests/overview/Overview.vue';
import {shallowMount, createLocalVue} from "#vue/test-utils";
import Vuex from "vuex";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("CoachAppointmentsRequests", () => {
let wrapper;
let store;
let actions;
let state;
let getters;
const $route = {
path: 'appointment/requests/:application_id',
params: { application_id: 123 }
}
actions = {
GET_USER_APPOINTMENTS: jest.fn()
};
state = {
user_appointments: [ {id:1, date: 'May 20, 2020'} ],
all_user_appointments: [ {id:1, date: 'May 20, 2020'} ],
};
getters = {
user_appointments: state => state.user_appointments,
all_user_appointments: state => state.all_user_appointments
};
store = new Vuex.Store({
actions,
getters,
state,
});
const getUserAppointments = jest.fn(() => {
return new Promise(resolve => {
process.nextTick(() => {
resolve({
data: [
{ id:1, appointee_id:2}
]
})
})
})
});
beforeEach(() => {
wrapper = shallowMount(CoachAppointmentsRequests, {
propsData: {},
mocks: {
$route,
},
stubs: {},
methods: {
getUserAppointments,
},
store,
localVue,
});
});
it('click on the view appointment button calls the viewAppointment method', () => {
const viewAppointment = jest.fn();
wrapper.setMethods({ viewAppointment })
const viewAppBtn = wrapper.find('#view-appointment');
viewAppBtn.trigger('click');
expect(viewAppointment).toBeCalled();
});
});
Please I will appreciate your assistance with this issue.

The click handler isn't called immediately after trigger(), but rather it's called in the next tick. However, trigger() returns a Promise that resolves when the component is updated, so you could await the result of the call, as shown in the docs example:
it('clicked it', async () => {
// ...
await viewAppBtn.trigger('click')
expect(viewAppointment).toBeCalled()
})

I had a similar problem. I've used shallowMount to mount vue component and click on button wasn't working. The solution was to change shallowMount to mount.

Related

Unit tests for alert controller in ionic

I am learning to write unit tests in ionic and am unable to write a test for AlertController. Below attached is the code
Terms.page.ts file
export class TermsPage {
constructor(private router: Router, private alertController: AlertController) {}
onAgreeClick() {
this.router.navigate(['/register']);
}
onDeclineClick() {
this.presentAlertConfirm();
}
async presentAlertConfirm() {
const alert = await this.alertController.create({
message: 'Please agree to our terms and conditions to be able to use this application!',
buttons: [
{
text: 'Agree',
cssClass: 'primary',
handler: () => {
this.onAgreeClick();
},
},
{
text: 'Maybe later',
role: 'cancel',
cssClass: 'secondry',
},
],
});
await alert.present();
}
}
Terms.spec.ts
import { DebugElement } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { Router, RouterModule } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { EmptyTestComponent } from '#test-utils';
import { TermsPage } from './terms.page';
fdescribe('TermsConditionsComponent', () => {
let component: TermsPage;
let fixture: ComponentFixture<TermsPage>;
let de: DebugElement;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TermsPage],
imports: [
RouterModule.forRoot([]),
RouterTestingModule.withRoutes([{ path: '**', component: EmptyTestComponent }]),
],
}).compileComponents();
fixture = TestBed.createComponent(TermsPage);
component = fixture.componentInstance;
de = fixture.debugElement;
router = TestBed.inject(Router);
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
it('should be able to agree and go to registration on click', async () => {
const agreeButton = de.query(By.css('#button-agree')).nativeElement as HTMLIonButtonElement;
agreeButton.click();
await fixture.whenStable();
expect(router.url).toBe('/register');
});
it('should be able to trigger popup on click of disagree click', async () => {
const disagreeButton = de.query(By.css('#button-disagree')).nativeElement as HTMLIonButtonElement;
disagreeButton.click();
await fixture.whenStable();
expect(component.presentAlertConfirm).toBeTruthy();
});
});
I need to hit the 100% coverage
Would really appreciate it if someone could help me write test case to cover the alert button actions and present. Thanks in advance
Looks like you need to split your test into two:
test #1 for alertcontroller.create usage - to has been called with proper arguments
and test #2 for button handlers
First can be easely emulated with standard jasmine calls like .toHaveBeenCalledWith(...):
const alertControllerStub = jasmine.createSpyObj('AlertController', ['create']);
...
expect(alertControllerStub.create).toHaveBeenCalledWith(options);
And the second one, you need to fire "ok"/"cancel" manually and catch the method executed for both cases
const ({buttons}) = alertControllerStub.create.calls.first().args[0];
buttons[0].handler();
expect(smth_should_called_in_handler).toHaveBeenCalled();

How to Unit Test a Method in a Vue.js Component using jest

I'm trying to unit test a component method. The question here does not lay out how to access the component method from a unit test.
Specifically, given my Vue component below, how do I access doSomeWork() from my unit test?
Vue component:
<template>
<div id="ThisStuff">
<span>
Some other stuff is going on here
</span>
</div>
</template>
<script>
import foo from 'bar'
export default {
props: {
ObjectWithStuffInIt: [
{
id: 1
bar: false
},
{
id: 2
bar: false
},
]
},
data: {
foo: "foo"
},
methods: {
doSomeWork: function() {
for (var i = 0; i < ObjectWithStuffInIt.length; i++) {
if (foo === "diddly") {
ObjectWithStuffInIt[i].bar = true;
}
}
}
}
}
</script>
My test code:
import {createLocalVue, shallow} from 'vue-test-utils'
import ThisVueFile.test.js from '../../thisPlace/ThatPlace/ThisVueFile.vue'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex);
describe('ThisVueFile.test.js', () => {
let user;
let store;
beforeEach(() => {
let getters = {
user: () => user
}
store = new Vuex.Store({ getters })
})
// I need to fill propsData: with some local data here
// because it is server data
// I need to have access to the method
// I need to use local data for `foo` in the test.
it(' When foo is set to -diddly- then set bar to true ', () => {
foo = "diddly";
// run the method in the component here
doSomeWork();
expect(OjbectWithStuffInIt[0].bar.equals(true));
})
})
Calling component method
The wrapper provides access to the component instance via its vm property, so you could call the method directly with:
wrapper.vm.doSomeWork()
Setting props
The mounting options (passed to shallowMount() or mount()) include the propsData property that could be used to initialize the component's props before mounting.
You could also use the wrapper's setProps() after the component has already been mounted.
Example:
it('...', () => {
const wrapper = shallowMount(MyComponent, {
propsData: {
myItems: [
{ id: 200, bar: false },
{ id: 300, bar: false }
]
}
});
// OR
wrapper.setProps({
myItems: [
{ id: 400: bar: true }
]
})
})
Modifying component data property
The mounting options includes the data property that could be used to initialize the component's data before mounting.
You could also use the wrapper's setData() after the component has already mounted.
You could access the component's data property directly through the wrapper's vm property.
Example:
it('...', () => {
const wrapper = shallowMount(MyComponent, {
data() {
return {
foo: 1
}
}
});
// OR
wrapper.setData({ foo: 2 })
// OR
wrapper.vm.foo = 3
})
Full example
Altogether, your test might look similar to this:
import { createLocalVue, shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent'
describe('MyComponent', () => {
it('When foo is set to -something-, set bar to true', () => {
const myItems = [
{ id: 200, bar: false },
{ id: 300, bar: false }
]
const localVue = createLocalVue()
const wrapper = shallowMount(MyComponent, {
localVue,
propsData: {
myItems
}
})
wrapper.vm.foo = 'something'
wrapper.vm.doSomeWork()
expect(myItems[0].bar).toBe(true)
})
})
demo

Vue Test Utils / Jest - How to test if class method was called within a component method

I have an interesting problem with a unit test of mine. My unit test is written to click on a button inside a component. This button calls a component method which contains an instance of a class Service (a wrapper class for axios). The only thing this component method does is call Service.requestPasswordReset(). My unit test needs to verify that Service.requestPasswordReset was called.
I know I'm mocking my Service class correctly, because this passes in my unit test:
await Service.requestPasswordReset()
expect(Service.requestPasswordReset).toHaveBeenCalled()
And I know that I'm calling the method correctly on click because this passes in my unit test:
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
I just can't get my test to register that the Service method gets called. Any ideas?
Component
<template lang="pug">
Layout
section
header( class="text-center py-4 pb-12")
h1( class="text-grey-boulder font-light mb-4") Recovery Email
p( class="text-orange-yellow") A recovery email has been sent to your email address
div( class="text-center")
div( class="mb-6")
button(
type="button"
#click.stop="resend()"
class="bg-orange-coral font-bold text-white py-3 px-8 rounded-full w-48"
) Resend Email
</template>
<script>
import Layout from '#/layouts/MyLayout'
import Service from '#/someDir/Service'
export default {
name: 'RecoveryEmailSent',
page: {
title: 'Recovery Email Sent',
},
components: {
Layout,
},
data() {
return {
errorMessage: null
}
},
computed: {
userEmail() {
const reg = this.$store.getters['registration']
return reg ? reg.email : null
},
},
methods: {
async resend() {
try {
await Service.requestPasswordReset({
email: this.userEmail,
})
} catch (error) {
this.errorMessage = error
}
},
},
}
</script>
Service.js
import client from '#/clientDir/BaseClient'
class Service {
constructor() {
this.client = client(baseUrl)
}
requestPasswordReset(request) {
return this.client.post('/account_management/request_password_reset', request)
}
}
export { Service }
export default new Service()
Service.js in __mock__
export default {
requestPasswordReset: jest.fn(request => {
return new Promise((resolve, reject) =>
resolve({
data: {
statusCode: 'Success',
},
})
)
})
}
Unit Test
jest.mock('#/someDir/Service')
import { shallowMount, mount, createLocalVue } from '#vue/test-utils'
import RecoveryEmailSent from './AccountManagement.RecoveryEmailSent'
import Service from '#/someDir/Service'
const localVue = createLocalVue()
// localVue.use(Service) // <-- Tried this, didn't work
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const mockMethods = {
resend: jest.fn()
}
const email = 'testemail#test.com'
const wrapper = mount(RecoveryEmailSent, {
localVue,
computed: {
userEmail() {
return email
},
},
methods: mockMethods
})
// await Service.requestPasswordReset()
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(Service.requestPasswordReset).toHaveBeenCalled()
})
})
I figured it out. Apparently, Jest's .toHaveBeenCalled() doesn't return true if the method in question was called with parameters. You MUST use .toHaveBeenCalledWith(). I don't see anything about this caveat in their docs, but it does seem to be the case.
Here is my passing test code
it('should resend email hash', async () => {
const email = 'testemail#test.com'
const wrapper = mount(AccountManagementForgottenPasswordSubmitted, {
localVue,
computed: {
userEmail() {
return email
},
},
})
await wrapper.find('button').trigger('click')
expect(Service.requestPasswordReset).toHaveBeenCalledWith({
email: email
})
})
You can use inject-loader to mock your Service
Basic idea:
const RecoveryEmailSentInjector = require('!!vue-loader?inject!./AccountManagement.RecoveryEmailSent')
import Service from '#/someDir/Service'
const mockedServices = {
'#/someDir/Service': Service
}
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const RecoveryEmailSentWithMocks = RecoveryEmailSentInjector(mockedServices)
const wrapper = mount(RecoveryEmailSentWithMocks, {
...
})
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(mockedServices.requestPasswordReset).toHaveBeenCalled()
})
})

Async Jest test in vueJS lifecycle hook with Interval

I would to understand how can i test my "auth/refresh" action in the "beforeCreate" hook with jest like below :
// main.vue
async beforeCreate() {
let authTokenRefreshIntervalId;
await this.$store.dispatch('auth/initialize');
authTokenRefreshIntervalId = setInterval(() => {
this.$store.dispatch('auth/refresh').catch(() => {
this.$store.dispatch('auth/logout');
clearInterval(authTokenRefreshIntervalId);
});
}, 30 * 1000);
}
// main.spec.js
import Vue from 'vue';
import Vuex from 'vuex';
import { shallow, createLocalVue, mount } from '#vue/test-utils';
import Main from '#/main';
const localVue = createLocalVue();
jest.useFakeTimers();
describe('store-auth', () => {
let store;
let actions;
let getters;
beforeEach(() => {
actions = {
initialize: jest.fn(),
refresh: jest.fn(),
logout: jest.fn(),
};
getters = {
isAuthenticated: jest.fn(),
};
store = new Vuex.Store({
modules: {
auth: {
namespaced: true,
actions,
getters,
},
},
});
});
it('dispatch initialize on beforeCreate hook', () => {
const wrapper = shallow(Main, { store, localVue });
expect(actions.initialize).toHaveBeenCalled();
});
it('dispatch refresh on beforeCreate hook every 30s', () => {
const wrapper = shallow(Main, { store, localVue });
jest.runTimersToTime(30 * 1000);
expect(actions.refresh).toHaveBeenCalled();
});
});
Jest say that the mocked function is not called.
I tried with expect(setInterval).toHaveBeenCalled() and it pass the test.
Where i'm wrong plz ?
Try using async/await in your test also.
it('dispatch refresh on beforeCreate hook every 30s', async () => {
const wrapper = shallow(Main, { store, localVue });
jest.runTimersToTime(30 * 1000);
await expect(actions.refresh).toHaveBeenCalled();
});

How to unit test VueJS watcher on $route

I'm testing a Single file component that uses vue router to watch $route. The problem is that I can't get the test to both change the route and trigger the watcher's function.
The test file:
import { createLocalVue, shallow } from 'vue-test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
const localVue = createLocalVue();
localVue.use(Vuex);
const $route = {
path: '/my/path',
query: { uuid: 'abc' },
}
wrapper = shallow({
localVue,
store,
mocks: {
$route,
}
});
it('should call action when route changes', () => {
// ensure jest has a clean state for this mocked func
expect(actions['myVuexAction']).not.toHaveBeenCalled();
vm.$set($route.query, 'uuid', 'def');
//vm.$router.replace(/my/path?uuid=def') // tried when installing actual router
//vm.$route.query.uuid = 'def'; // tried
//vm.$route = { query: { uuid: 'def'} }; // tried
expect(actions['myVuexAction']).toHaveBeenLastCalledWith({ key: true });
});
My watch method in the SFC:
watch: {
$route() {
this.myVuexAction({ key: true });
},
},
How do you mock router in such a way that you can watch it and test the watch method is working as you expect?
This is how I'm testing a watch on route change that adds the current route name as a css class to my app component:
import VueRouter from 'vue-router'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import MyApp from './MyApp'
describe('MyApp', () => {
it('adds current route name to css classes on route change', () => {
// arrange
const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter({ routes: [{path: '/my-new-route', name: 'my-new-route'}] })
const wrapper = shallowMount(MyApp, { localVue, router })
// act
router.push({ name: 'my-new-route' })
// assert
expect(wrapper.find('.my-app').classes()).toContain('my-new-route')
})
})
Tested with vue#2.6.11 and vue-router#3.1.3.
I checked how VueRouter initializes $route and $router and replicated this in my test. The following works without using VueRouter directly:
const localVue = createLocalVue();
// Mock $route
const $routeWrapper = {
$route: null,
};
localVue.util.defineReactive($routeWrapper, '$route', {
params: {
step,
},
});
Object.defineProperty(localVue.prototype, '$route', {
get() { return $routeWrapper.$route; },
});
// Mock $router
const $routerPushStub = sinon.stub();
localVue.prototype.$router = { push: $routerPushStub };
const wrapper = shallowMount(TestComponent, {
localVue,
});
Updating $route should always be done by replacing the whole object, that is the only way it works without using a deep watcher on $route and is also the way VueRouter behaves:
$routeWrapper.$route = { params: { step: 1 } };
await vm.wrapper.$nextTick();
Source: install.js
Its working for me
let $route = {
name: 'any-route',
};
We defined a $route and we called like
wrapper = mount(YourComponent, {
mocks: {
$route,
},
});
and my componente is like this
#Watch('$route', { deep: true, immediate: true, })
async onRouteChange(val: Route) {
if (val.name === 'my-route') {
await this.getDocumentByUrl();
await this.allDocuments();
}
};
pd: I use typescript, but this work with the another format
and finally my test
it('my test', ()=>{
const getDocumentByUrl = jest.spyOn(wrapper.vm, 'getDocumentByUrl');
const allDocuments = jest.spyOn(wrapper.vm, 'allDocuments');
wrapper.vm.$route.name = 'my-route';
await flushPromises();
expect(getDocumentByUrl).toHaveBeenCalled();
expect(allDocuments).toHaveBeenCalled();
})
The way to do this actually is to use vue-test-utils wrapper method, setData.
wrapper.setData({ $route: { query: { uuid: 'def'} } });