vue jest spyOn not working when calculating watcher method calls - vue.js

I'm getting familiar with jest and vue and I wanted to see how to make sure a method fired when a prop changed. In this particular scenario it's trivial and this seemed straight forward. But it's not working.
Components Watcher
#Watch("id")
public async idChanged() {
this.calculateStatus();
}
beforeEach - this initilizes the wrapper for each test
beforeEach(async () => {
var httpClient = new PemHttpClient(vue);
var v3ReferenceDatumService = new V3ReferenceDatumService(httpClient, "");
var contractService = new V3ContractService(httpClient, "", v3ReferenceDatumService);
wrapper = mount(AmendmentIdDisplay, {
provide: {
v3ContractService: contractService,
},
propsData: {
id: "82.5.1"
}
});
await wrapper.vm.$nextTick();
})
Jest Test
let calculateFired = jest.spyOn(wrapper.vm, "calculateStatus");
wrapper.setProps({
...wrapper.props(),
id: "1"
})
await wrapper.vm.$nextTick();
expect(calculateFired).toBeCalled();
I would expect the spy to have incremented the call counter but it does not. It remains at zero. If I manually call wrapper.vm.calculateStatus(), the spy works correctly. So the setProps is either not firing the watcher at all, or some weird reference thing is happening which is causing the method that is called within the watcher, to not be the method I'm spying on. I'm not sure which.

I hope it is not too late. Yes there is a problem with jest.spyOn() and vue watchers. I have a trick that patch the problem for now (tested on sync function only) :
const insertSpyWatcher = (vueInstance: any, watcherExpression: string, spyInstance: jest.SpyInstance) => {
let oldWatcherIndex = -1;
let deep = false; // pass the deep option value from the original watcher to the spyInstance
// find the corresponding watcher
vueInstance._watchers.forEach((watcher: any, index: number) => {
if (watcher.expression === watcherExpression) {
oldWatcherIndex = index;
deep = watcher.deep;
}
});
// remove the existing watcher
if (oldWatcherIndex >= 0) {
vueInstance._watchers.splice(oldWatcherIndex, 1);
} else {
throw new Error(`No watchers found with name ${watcherExpression}`);
}
// replace it with our watcher
const unwatch = vueInstance.$watch(watcherExpression, spyInstance, { deep });
return unwatch;
};
Then in your test :
it('test the watcher call', () => {
let calculateFired = jest.spyOn(wrapper.vm, "calculateStatus");
insertSpyWatcher(wrapper.vm, "id", calculateFired) // Yes 'id' is the name of the watched property
wrapper.setProps({
...wrapper.props(),
id: "1"
})
await wrapper.vm.$nextTick();
expect(calculateFired).toBeCalled();
});
If the immmediate property is needed, you can always add it as argument of insertSpyWatcher. I did not find a way to get the immediateproperty of the original watcher.

Related

How do I use #vue/test-utils to setProps and have them show up in the HTML?

I have the my code working in a sandbox and now I am trying to write the test. However, When I try this...
test("Hello World", async () => {
let list = [
{
name: "foo"
}
];
var data = {
list
};
const wrapper = mount(MyComponent, data);
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).not.toContain("bar");
list.push({
name: "bar"
});
await wrapper.setProps({ list });
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).toContain("bar");
});
However, expect(wrapper.html()).toContain("bar"); fails because it cannot fine the text. I can see it work using setTimeout so I am not sure what I am missing.
How do I see the prop changes in the html?
Your component is not expecting any props. When you mounting your component you are setting component's data property. And if you want to change it later in test after mounting you should call setData.
Also there is a mistake in your test: according to docs data must be a function.
With all being said your test should look like that:
test("Hello World", async () => {
const list = [
{
name: "foo"
}
];
const data = () => {
list
};
const wrapper = mount(MyComponent, {
data
});
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).not.toContain("bar");
list.push({
name: "bar"
});
await wrapper.setData({ list });
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).toContain("bar");
});

Unit test jest enzyme throws error on Formik 'resetForm'

I am trying to run unit test (enzyme) throws error on Formik 'resetForm'.
TypeError: Cannot read property 'resetForm' of undefined
FormikForm.js
_handleSubmitPress = (values, { resetForm }) => {
const { onSubmit } = this.props;
if (onSubmit) {
onSubmit({ ...values, resetForm });
}
};
UnitTest.js:
it('Should fire formik form submit', () => {
const UpdateButtonPressMock = jest.fn();
const component = Component({
onSubmit: UpdateButtonPressMock,
});
expect(component.find(Formik)).toHaveLength(1);
component.find(Formik)
.first()
.simulate('Submit');
expect(UpdateButtonPressMock).toHaveBeenCalled();
});
I couldn't find any solution for this error.
Could someone help me on the above? I would really appreciate any help.
According to official docs for simulate, the function signature accepts an optional mock event.
The code you are testing uses properties that are not included in the default SyntheticEvent object that ReactWrapper passes to your event handler by default, for instance event.resetForm.
One way to do this is by triggering Formik's onSubmit directly like so:
// UnitTest.js
.simulate("submit", { resetForm: whateverYourMockResetFormValueShouldBe })
component.find(Formik)
.first()
.prop('onSubmit')(valuesMock, { resetForm: UpdateButtonPressMock });
expect(UpdateButtonPressMock).toHaveBeenCalled();
I haven't tested this, but you should be able to pass the event along with simulate as well.
// UnitTest.js
component.find(Formik)
.first()
.simulate("submit", { resetForm: UpdateButtonPressMock })
expect(UpdateButtonPressMock).toHaveBeenCalled();

AddEventListener DOM event Jest testing

I have one Users vue component and I am trying to test mounted() with addEventListener.
Users.vue
=========
mounted(){
let viewPort = document.getElementById("Users-list"); ----> Here I am getting null for addEventListener.
viewPort!.addEventListener("scroll", (e: any) => {
let result =
e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight ===
0;
if (result) {
this.test = this.Offset + this.Limit;
this.response = this.GetDetails();
}
});
}
I have written spec for Users component and trying to test mounted() method with addEventListener.
But I am getting an error message cannot read property addEventListener of null.
Users.spec.ts
=============
describe('Users TestSuite', async () => {
let userWrapper: any;
let userObj: any;
beforeEach(() => {
userWrapper = shallowMount(Users, {
// attachTo: document.getElementById('Users-list'),
localVue,
i18n,
router
})
userObj = userWrapper.findComponent(Users).vm;
const mockAddeventListener = jest.fn().mockImplementation((event, fn) => {
fn();
})
document.getElementById = jest.fn().mockReturnValue({
scrollTop: 100,
clientHeight: 200,
scrollHeight: 500,
addEventListener: mockAddeventListener
})
expect(mockAddeventListener).toBeCalledWith('scroll', expect.anything());
});
it('should render Users page', () => {
expect(userObj).toBeTruthy();
});
I think the problem here might be u are creating the mock function after u are creating the component. Mounted method will be called when the wrapper is created so try to move the mock implementation above the wrapper statement.
Another sure way in which to make it work is before u create the wrapper set the body of the document like document.body.innerHTML = <div id="users-list"></div>. This will definitely work.
For both the above solutions make sure that they are above the wrapper statement.

Focus event unit test doesn't works in Vuejs / Jest

I want to create a unit test for two events, on focus and on blur.
I am using vueJS and jest.
handleFocus(event) {
if (this.blured === true)
if (event.relatedTarget !== null) {
this.blured = event.relatedTarget.className
.toString()
.includes("datepicker");
} else this.blured = false;
}
That's what i tried, but the method seems not to be called
beforeEach(() => {
mocks = {
$t: jest.fn()
};
});
it("calls 'handleFocus' on focus", async () => {
const wrapper = mount(CxpDatepicker, {
mocks,
localVue
});
const input = wrapper.find("input");
wrapper.vm.handleFocus = jest.fn();
input.trigger("focus");
await localVue.nextTick();
expect(wrapper.vm.handleFocus).toHaveBeenCalled();
});
Please help pe to find the solution.
I understand I am very late to reply, but if you or anyone else still needs to know,
Try following to invoke your event:
input.element.dispatchEvent(new FocusEvent('focus'));
Instead of
input.trigger("focus");
I was also not able to invoke it. So I tried this way, and it worked for me.

Which Lifecycle hook after axios get but before DOM render

I'm trying to render my DOM, dependent on some data I'm returning from an axios get. I can't seem to get the timing right. The get is in the created hook, but there is a delay between the get and actually receiving the data. Basically if there is info in seller_id then I need to show the cancel button, otherwise don't. Here is my code:
this is in my created hook
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
and then this is the logic to show or hide the button. I've tried created, mounted, beforeUpdate, and updated all with no luck. I've also tried $nextTick but I can't get the timing correct. This is what I have currently:
beforeUpdate: function () {
// this.$nextTick(function () {
function sellerIdNotBlank() {
var valid = this.seller_id == '';
return !valid;
}
if(sellerIdNotBlank()){
this.show_cancel_button = true;
}
// })
},
First, it is pointless to get your data from backend and try to sync with Vue.js lifecycle methods. It never works.
Also, you should avoid beforeUpdate lifecycle event. It is often a code smell. beforeUpdate is to be used only when you have some DOM manipulations done manually and you need to adjust them again before Vue.js attempt to re-render.
Further, show_cancel_button is a very good candidate for a computed property. Here is how component will look:
const componentOpts = {
data() {
return {
seller_id: '',
// ... some more fields
};
},
created() {
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
},
computed: {
show_cancel_button() {
return this.seller_id !== '';
}
}
}