Closed TheoSl93 closed 6 years ago
First, have you tried this with beta.24?
If the problem persists, can you provide a runnable reproduction?
Hi, I've tried with beta.24 but the problem persists. I'll make a repository that you can fork. Thanks!
Hey, we just updated to beta.24 and have the same problem. We had to refactor some of our tests using trigger('click') to make them work again.
Here is a repo with a minimal runnable reproduction: https://github.com/TheoSl93/vueUtils-repro-929
Thanks!!
@TheoSl93 I pulled your repo and got it working. You can use setMethods
, then your test passes.
it('calls the toggleVisibility method when the icon is clicked', () => {
wrapper.setMethods({ toggleVisibility:jest.fn() })
wrapper
.find('.unread-messages')
.trigger('click')
expect(wrapper.vm.toggleVisibility).toHaveBeenCalledTimes(1)
})
It works! Thanks a lot for the tip. What's the difference between the two methods, by the way?
setMethods
updates the instance, which re render vnodes (and ultimately Elements) to use the new method in their on handlers.
I'm going to close this issue, since we aren't able to change this behavior in Vue Test Utils.
The recommended way to set a method is to use setMethods
Problem has been resolved. I'm using beta.28. I just want to know why we should using setMethods
update the instance?
In the code example above, why don't wrapper.find('.unread-messages').trigger('click')
share the same instance?
This is not a issue, its just a question @eddyerburgh
https://github.com/vuejs/vue-test-utils/issues/983#issuecomment-425468635
Why we need to re render the component after stubing a components methods?
Stub replace the Object reference and Vue is not reactive to the change of Object, which is similar to the princeple for normal Object defined as Vue data.
Is my assumption right?
Hi everyone,
I'm using beta.29 and this problem still seems to persist.
I have the following template
<template>
<b-input-group class="sm-2 mb-2 mt-2">
<b-form-input
:value="this.searchConfig.Keyword"
@input="this.updateJobsSearchConfig"
class="mr-2 rounded-0"
placeholder="Enter Search term..."
id="input-keyword"
/>
<b-button
@click="searchJobs"
class="rounded-0"
variant="primary"
id="search-button"
>
Search
</b-button>
<b-button
@click="resetFilter"
class="rounded-0 ml-2"
variant="primary"
id="reset-button"
>
Reset
</b-button>
</b-input-group>
</template>
My tests are:
it('should call searchJobs method on search button click event', async () => {
wrapper.find('#search-button').trigger('click')
expect(await searchJobs).toHaveBeenCalled()
})
//it('should call resetFilter method on reset button click event', () => {
//wrapper.find('#reset-button').trigger('click')
//expect(resetFilter).toHaveBeenCalled()
// })
it('should call resetFilter method on reset button click event', () => {
wrapper.setMethods({ resetFilter: jest.fn() })
wrapper.find('#reset-button').trigger('click')
expect(wrapper.vm.resetFilter).toHaveBeenCalled()
})
The first test passes successfully and it has searchJobs
mocked inside shallow mounted wrapper like so
wrapper = shallowMount(JobsSearch, {
methods: {
updateJobsSearchConfig,
searchJobs,
resetFilter,
emitEvents
},
localVue,
store })
As you can see I, I tried mocking resetFilter
and running the same test as with searchJobs
but calling the correct function which didn't work. Then I tried using setMethods
as @lmiller1990
lmiller1990 suggested but Im still getting 'Expected mock function to have been called, but it was not called.' on the second test.
Any help will be greatly appreciated.
Hey @BorisAng , can you post the entire test (with how you do jest.fn
) etc? Or - ideally a repo that I can pull and repro the error would be ideal. The code looks okay, but it's possible something else is incorrect (or there is a bug).
@lmiller1990 here is the test file:
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
// BootstrapVue is necessary to recognize the custom HTML elements
import BootstrapVue from 'bootstrap-vue'
import JobsSearch from '@/components/jobs/JobsSearch.vue'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(BootstrapVue)
describe('JobsSearch.vue', () => {
let actions
let state
let store
let wrapper
let updateJobsSearchConfig
let searchJobs
let resetFilter
let emitEvents
beforeEach(() => {
state = {
jobs: {
paged: {
size: 100,
page: 1
},
search: {
Keyword: '',
status: [],
ucode: []
}
}
}
actions = {
updateJobsPagedConfig: jest.fn()
}
store = new Vuex.Store({
actions,
state
})
updateJobsSearchConfig = jest.fn()
searchJobs = jest.fn()
resetFilter = jest.fn()
emitEvents = jest.fn()
wrapper = shallowMount(JobsSearch, {
methods: {
updateJobsSearchConfig,
searchJobs,
resetFilter,
emitEvents
},
localVue,
store })
})
afterEach(() => {
wrapper.destroy()
})
// Tests go here, I have provided them above
})
The JobsSearch
component also uses Vuex and has some actions, so I have mocked these. As you can see, I am mocking searchJobs
and resetFilter
in exactly the same way and then running pretty much the same tests just using the two functions respectively.
I have a similar problem. I don't understand why my cancelAlert() method is not called in tests... The strangest thing is that the code inside the function is executed... Please, advice. @eddyerburgh
@TheoSl93 I pulled your repo and got it working. You can use
setMethods
, then your test passes.it('calls the toggleVisibility method when the icon is clicked', () => { wrapper.setMethods({ toggleVisibility:jest.fn() }) wrapper .find('.unread-messages') .trigger('click') expect(wrapper.vm.toggleVisibility).toHaveBeenCalledTimes(1) })
Why do I have to set a method which already exists in a wrapper instance? @lmiller1990 @eddyerburgh
@nickelaos What happens if you do wrapper.vm.$options.methods.cancelAlert
? Not exactly sure why, but that might work. Just doing a console.log
on wrapper.vm.cancelAlert
vs wrapper.vm.$options.methods.cancelAlert
:
[Function: bound greet]
and [Function: greet]
respectively. Can you try that? That might help you too, @nickelaos .
@lmiller1990 Sorry, I didn't get your point. I can call wrapper.vm.cancelAlert(), and the test passes in this case. But I don't want to call it directly. I want to check if it is called after the button is clicked. It is not. That is the problem.
@nickelaos maybe I misunderstood. You said that you do jest.spyOn(wrapper.vm, 'cancelAlert')
, then call shouldHaveBeenCalled()
and it is false
. I was suggesting you try spyOn(wrapper.vm.$options.methods, 'cancelAlert')
- although this might be incorrect. I believe Vue saves all methods in an $options
key under the hood, and if you want to do spyOn
, you need to spyOn that method, not vm.cancelAlert
(which likely calls $options.methods.cancelAlert
in the end).
@lmiller1990 Didn't help:
Hm, not too sure then... sorry. If you can make a minimal repo, I can try debugging a little more.
Same here. Bound method is not called when triggering the click on the button. Data attributes are not updated, etc... I don't understand as it feels rather straight forward. Is there anything I miss?
Upgraded from 3.12.0
to 4.0.5
, but does nothing.
I run inside docker
, with node:lts-alpine
.
If you shallowMount and if you're button comes from a framework, then it is stubbed and the click action is not accessible.
Luckily I already had a test in which a click event was triggered, and I was able to understand the very subtle difference between tests passing and tests failing: it's the parenthesis in the function call.
So basically the very same test:
test('Click calls the right function', () => {
// wrapper is declared before this test and initialized inside the beforeEach
wrapper.vm.testFunction = jest.fn();
const $btnDiscard = wrapper.find('.btn-discard');
$btnDiscard.trigger('click');
expect(wrapper.vm.testFunction).toHaveBeenCalled();
});
was failing with this template:
<button class="btn blue-empty-btn btn-discard" @click="testFunction">
{{ sysDizVal('remove') }}
</button>
while it passed with this subtle change:
<button class="btn blue-empty-btn btn-discard" @click="testFunction()">
{{ sysDizVal('remove') }}
</button>
I've seen that in the original post the parenthesis were missing, so I assume this answers the question.
I hope this behavior will be fixed in future releases because the syntax without parenthesis is somehow encouraged in all the docs I have seen.
Hope this helps.
When I do that and remove the ()
, the mock is not even applied; my original method is triggered. 🤔
I am not sure overriding a method by doing wrapper.vm.testMethod
is advisable. Modifying internals, unsuprisingly, leads to unexpected side effects.
Overriding a method dynamically like this is not documented here or in Vue's core docs from what I can see. Vue has some magic to call functions even if you invoke them without ()
as a @click
listeners (passing $event
as the first arg), and I suspect by reassigning vm.testMethod
to jest.fn
that magic goes away.
What I think we need is a better way of testing this; the current behavior is not really a bug per-se, but a result of how Vue works. If you want to assert something happens when a method is called, ideally you would assert the effect of that method. Eg, if it is an API call with axios
, you can do jest.mock
and assert that module was called.
According to the docs, the official way to mock a method is:
wrapper.setMethods({ clickMethod: clickMethodStub })
But in the same docs they say that it is deprecated.
Anyway, if I test he function mocking it like this:
wrapper.vm.testFunction = jest.fn();
or like this:
wrapper.setMethods({ testFunction: jest.fn() });`
the issue is still the same: my test succeeds if the method is called in the template with the parenthesis, otherwise it fails.
Official syntax (deprecated), with parenthesis in the template
Official syntax (deprecated), without parenthesis in the template
Just replace it before mounting. This is not an advisable way to test anyway, like Lachlan said.
@dobromir-hristov How do you replace a method before mounting? Can you show me an example?
merge(yourComponent, { methods: { myMethod: () => jest.fn() } }
Note: merge
is likely from the lodash library. It is not part of VTU.
Ok, that's why it gave an error:
ReferenceError: merge is not defined
Ok thanks to this answer on Stack Overflow I've found how to mock the method before mounting:
mock + mount
const testFunctionSpy = jest.spyOn(Component.methods, 'testFunction');
const wrapper = shallowMount(Component);
In this way I can test that the function is called even without the parentheses.
spec
expect(testFunctionSpy).toHaveBeenCalled();
template
<button class="btn blue-empty-btn btn-discard" @click="testFunction">
{{ sysDizVal('remove') }}
</button>
Good day, I am have issue testing click on my application, I have tried all the methods mention here; none is working . Please can anyone provide me an example to follow, I will really appreciate Thanks.
@ovuefe Did you try following this Stack Overflow question with the answer?
Yes, this is my test it('delete assignment', async ()=>{ const btnDelete= jest.spyOn(wrapper.vm, 'handleDeleteAssignment'); wrapper.find('#deleteAssignment').trigger('click') expect(btnDelete).toHaveBeenCalled()
})
This is the error message Error: expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1 Received number of calls: 0
You have to instantiate the spy before the wrapper, and so the method you spy should be taken directly from the component and not from the wrapper, like this:
btnDeleteSpy = jest.spyOn(Modal.methods, 'handleDeleteAssignment');
Here is an example taken from a real test, with some variables renamed after your example and some details omitted:
import { shallowMount } from '@vue/test-utils';
import Modal from '@/components/modals/Modal.vue';
describe('Modal.vue', () => {
let wrapper;
let btnDeleteSpy;
beforeEach(() => {
// create the spy before instantiating the wrapper
btnDeleteSpy = jest.spyOn(Modal.methods, 'handleDeleteAssignment');
// instantiate the wrapper
wrapper = shallowMount(Modal);
});
afterEach(() => {
// this is to count correctly how many times a function has been called
jest.clearAllMocks();
});
test('delete assignment', () => {
const $btnDiscard = wrapper.find('#deleteAssignment');
$btnDiscard.trigger('click');
expect(btnDeleteSpy).toHaveBeenCalled();
});
});
This is my entire code
handleDeleteAssignment = jest.spyOn(Assignment.methods,'handleDeleteAssignment')
wrapper = shallowMount(Assignment,{localVue,store,
data(){
return{
loading:false,
fileTextSvg:'',
all_assignment:['name']
}
},
mocks:{
$route,
},
})
})
afterEach(()=>{
jest.clearAllMocks()
})
it('delete assignment', async ()=>{
const $btnDiscard = wrapper.find('#deleteAssignment');
$btnDiscard.trigger('click');
expect(handleDeleteAssignment).toHaveBeenCalled();
})
The same result:
--->
Please share your entire component, I'll make a recommendation on how to test it. There is probably a better way than using spy.
Good morning @lmiller1990 and the entire team This is the entire test component
import Vue from "vue"
import {createLocalVue, mount, shallowMount} from "@vue/test-utils";
import AssignmentAssessment
from "@/trainingProvider/components/classes/class/components/assessment/AssignmentAssessment";
import Assignment from "@/trainingProvider/components/classes/class/components/assignment/Assignment";
import Vuetify from "vuetify";
import Vuex from "vuex";
import VueRouter from 'vue-router';
import {ValidationObserver,ValidationProvider} from 'vee-validate'
import flushPromises from 'flush-promises'
import {greaterThan} from "simple-vue-validator/src/templates";
import {methods} from "vue-json-to-csv/src/utils/helpers";
const localVue = createLocalVue();
localVue.use(Vuex);
Vue.use(Vuetify)
// Vue.use(Vuetify);
// Vue.use(VueRouter)
// const router = new VueRouter();
describe("Assignment assessment test",()=>{
let wrapper;
let store;
let $route;
let classAssessment;
let handleDeleteAssignment;
beforeEach(()=>{
classAssessment ={
namespaced : true,
getters:{
all_assignment : jest.fn()
},
state:{
all_assignment : ['name']
},
actions:{
getAllAssignment : jest.fn(() => Promise.resolve()),
handleStopAssignment : jest.fn(()=> Promise.resolve()),
},
}
$route={
params:{
id: '198',
name : 'sweet boy'
}
}
store = new Vuex.Store({
modules:{
classAssessment
}
})
handleDeleteAssignment = jest.spyOn(Assignment.methods,'handleDeleteAssignment')
wrapper = shallowMount(Assignment,{localVue,store,
data(){
return{
loading:false,
fileTextSvg:'',
all_assignment:['name']
}
},
mocks:{
$route,
},
})
})
afterEach(()=>{
jest.clearAllMocks()
})
it('should mount assignment component', function () {
expect(wrapper.exists).toBeTruthy()
});
it('due date should be one day ahead of today', function () {
let today = new Date();
let dueDate = new Date();
dueDate.setDate(today.getDate()+1)
wrapper.vm.$data.date=dueDate.getDate().toLocaleString()
console.log(wrapper.vm.$data.date)
expect(Number.parseInt(wrapper.vm.$data.date)).toBeGreaterThan(today.getDate())
});
it('all assignment to be called once ', async function () {
expect(classAssessment.actions.getAllAssignment.mock.calls).toHaveLength(1)
});
it('delete assignment', async ()=>{
const $btnDiscard = wrapper.find('#deleteAssignment');
$btnDiscard.trigger('click');
expect(handleDeleteAssignment).toHaveBeenCalled();
})
it('should click delete assignment button',()=>{
expect(wrapper.find('#deleteAssignment').trigger('click')).toBeTruthy()
})
})
I cannot run this code as is - it has a bunch of unknown imports.
I'll need something I can run locally, unforutantely I don't have time to figure out the config etc to run this example.
You probably need to add parentheses to the event handler on the child component. For example when testing from a parent component that a child emits an event and calls the appropriate function...
This works:
<MainTopbar @account="handleTopbarAccountClick()" />
This does not:
<MainTopbar @account="handleTopbarAccountClick" />
Version
1.0.0-beta.19
Reproduction link
https://codesandbox.io/s/2vlyrzkm1p
Steps to reproduce
The next test in jest does not work with the code in the example:
Only if
click
is changedclick.self
does the test work, but in that case no component inside can be clicked (and that's what I want to achieve)What is expected?
The test pass whether
click
orclick.self
is wrote.What is actually happening?
The test fails.
A
console.log
of the wrapper shows that it has the class in the wrapper, so it should be found and triggered.