Async lifecycle function in Vue Test Utils - vue.js

When I tried to test the component which has mounted method like this:
mounted(){
this.search()
}
methods:{
async search(){
try{
await axios.something
console.log("not executed only when shallowMount")
}catch{}
}
}
I checked it returned Promise<pending> without await.
I wrote the test like this:
wrapper = await shallowMount(Component, {
localVue
});
await wrapper.vm.search()// this works perfectly
However, only the shallowMount apparently skips awaited function while the next line works perfectly.
I have no idea about this behavior.
How can I fix it?
Edit:
I also use Mirage.js for mocking response.
function deviceServer() {
return createServer({
environment: "test",
serializers: {
device: deviceListSerializer()
},
models: {
device: Model
},
fixtures: {
devices: devices
},
routes() {
this.namespace = "/api/v1/";
this.resource("device");
},
seeds(server) {
server.loadFixtures("devices");
}
});
}

shallowMount is not returning Promise and that's why there is nothing to await. To await promises in tests try to use flush-promises library. With flushPromises your test will look like this:
import flushPromises from 'flush-promises'
wrapper = shallowMount(Component, {
localVue
});
await flushPromises(); // we wait until all promises in created() hook are resolved
// expect...

Related

Using XState in Nuxt 3 with asynchronous functions

I am using XState as a state manager for a website I build in Nuxt 3.
Upon loading some states I am using some asynchronous functions outside of the state manager. This looks something like this:
import { createMachine, assign } from "xstate"
// async function
async function fetchData() {
const result = await otherThings()
return result
}
export const myMachine = createMachine({
id : 'machine',
initial: 'loading',
states: {
loading: {
invoke: {
src: async () =>
{
const result = await fetchData()
return new Promise((resolve, reject) => {
if(account != undefined){
resolve('account connected')
}else {
reject('no account connected')
}
})
},
onDone: [ target: 'otherState' ],
onError: [ target: 'loading' ]
}
}
// more stuff ...
}
})
I want to use this state machine over multiple components in Nuxt 3. So I declared it in the index page and then passed the state to the other components to work with it. Like this:
<template>
<OtherStuff :state="state" :send="send"/>
</template>
<script>
import { myMachine } from './states'
import { useMachine } from "#xstate/vue"
export default {
setup(){
const { state, send } = useMachine(myMachine)
return {state, send}
}
}
</script>
And this worked fine in the beginning. But now that I have added asynchronous functions I ran into the following problem. The states in the different components get out of sync. While they are progressing as intended in the index page (going from 'loading' to 'otherState') they just get stuck in 'loading' in the other component. And not in a loop, they simply do not progress.
How can I make sure that the states are synced in all my components?

How to test Vue Component method call within an async method

I believe I am struggling to properly mock my methods here. Here is my situation, I have a component with two methods;
name: 'MyComponent',
methods: {
async submitAction(input) {
// does await things
// then ...
this.showToastMessage();
},
showToastMessage() {
// does toast message things
},
}
And I want to write a test that will assert that showToastMessage() is called when submitAction(input) is called. My basic test looking something like this;
test('the toast alert method is called', () => {
let showToastMessage = jest.fn();
const spy = jest.spyOn(MyComponent.methods, 'showToastMessage');
const wrapper = shallowMount(MyComponent, { localVue });
const input = // some input data
wrapper.vm.submitAction(input); // <--- this calls showToastMessage
expect(spy).toHaveBeenCalled();
};
NOTE: localVue is declare as such at the top of the file const localVue = createLocalVue();
I confirmed that both submitAction() and showToastMessage() methods are being called during the tests, by sneaking a couple of console.log()'s and observing it in the test output, however the test still fails;
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: called with 0 arguments
Number of calls: 0
566 | const wrapper = shallowMount(MyComponent, { localVue } );
567 | wrapper.vm.submitAction(input);
> 568 | expect(spy).toHaveBeenCalledWith();
I've tried spying on both methods as well
const parentSpy = jest.spyOn(MyComponent.methods, 'submitAction');
const spy = jest.spyOn(MyComponent.methods, 'showToastMessage');
// ...
expect(spy).toHaveBeenCalled()
same results, test fail.
What am I missing?
Tech Stack: vue 3, jest, node 14
#TekkSparrow you can pass a heap of stuff into the shallowMount function. It accepts an object as a second argument which can look something like
import { shallowMount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex)
let mocks = {
// this could be something like the below examples
// I had in a previous project
$route: {
query: '',
path: '/some-path'
},
$router: [],
$validator: {
validateAll: jest.fn()
},
$toast: {
show: jest.fn(),
error: jest.fn()
},
}
let propsData = {
// some props you want to overwrite or test.
// needs to be called propsData
}
let methods = {
showToastMessage: jest.fn()
}
let store = new Vuex.Store({
actions: {
UPLOAD_ASSET: jest.fn(),
},
})
const wrapper = shallowMount(MyComponent, { mocks, propsData, methods, store, localVue })
I believe that by doing similar to the above, your mocked function will run and be recorded by the Jest spy.
Took me a minute to realize/try this, but looks like since my calling function is async that I was suppose to make my test async, and await the main method call. This seems to have done the trick. Here's what ended up being my solution:
test('the toast alert method is called', async () => {
let showToastMessage = jest.fn();
const spy = jest.spyOn(MyComponent.methods, 'showToastMessage');
const wrapper = shallowMount(MyComponent, { localVue });
const input = // some input data
await wrapper.vm.submitAction(input);
expect(spy).toHaveBeenCalled();
};

Jest Testing in Vue

How would use Jest Test to test this method:
delayedFetch() {
setTimeout(() => {
this.fetchData();
}, 1000);
I have tried using Async and await but I'm prob using it wrong.
It's hard to test code with side effects, and you did not provide the entire context, but I try to help.
I think the this in the this.fetchData() inside the setTimeout is referencing the delayedFetch method itself. (I don't know it is your intention, as far as How I use vue.js
But anyway you can find, how to test setTimeouts here link to jest doc
Here is a simple implementation
const someObj = {
// I assume the delayedFetch is some method of an object
fetchData() {
return "some-data";
},
delayedFetch() {
const vue = this;
setTimeout(() => {
vue.fetchData();
}, 1000);
}
}
jest.useFakeTimers();
test("delayedFetchTest", () => {
someObj.delayedFetch();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
})

How to set mock nuxt asyncData in jest

I am using Nuxt.js and want to test my page which uses asyncData with Jest. I have a factory function to set up my wrapper, but it basically returns a shallowMount.
Expected
When clicking a button I want the function to behave differently depending on the query parameter. When running the test I want to mock this by setting it directly when creating the wrapper (Similar to setting propsData). E.g. const wrapper = factory({ propsData: { myQueryParam: 'some-value' } });
Result
However trying to set propsData still returns undefined: console.log(wrapper.vm.myQueryParam); // undefined while I would expect it to be 'some-value'
Question
Is there a different approach on how I can test this function that relies on query parameters?
Because asyncData is called before Vue is initialised, it means shallowMount doesn't work right out of the box.
Example:
page:
<template>
<div>Your template.</div>
</template>
<script>
export default {
data() {
return {}
},
async asyncData({
params,
error,
$axios
}) {
await $axios.get("something")
}
}
</script>
test:
import { shallowMount } from "#vue/test-utils";
describe('NewsletterConfirm', () => {
const axiosGetMock = jest.fn()
const axiosPostMock = jest.fn()
var getInitialised = async function (thumbprint) {
if (thumbprint == undefined) throw "thumbprint not provided"
let NewsletterConfirm = require('./_thumbprint').default
if (!NewsletterConfirm.asyncData) {
return shallowMount(NewsletterConfirm);
}
let originalData = {}
if (NewsletterConfirm.data != null) {
originalData = NewsletterConfirm.data()
}
const asyncData = await NewsletterConfirm.asyncData({
params: {
thumbprint
},
error: jest.fn(),
$axios: {
get: axiosGetMock,
post: axiosPostMock
}
})
NewsletterConfirm.data = function () {
return {
...originalData,
...asyncData
}
}
return shallowMount(NewsletterConfirm)
}
it('calls axios', async () => {
let result = await getInitialised("thumbprint")
expect(axiosGetMock).toHaveBeenCalledTimes(1)
});
});
Credits to VladDubrovskis for his comment: in this nuxt issue

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[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 m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.