I'm playing around with Vitest and want to wait for the completion of a couple mocked fetches in the onMounted lifecycle hook in my component:
My test:
import { mount } from '#vue/test-utils';
import HelloWorld from './HelloWorld.vue';
import { mockGet } from 'vi-fetch';
import 'vi-fetch/setup';
mockGet('api/welcome-message').willResolve('Welcome message from vitest');
mockGet('api/players').willResolve(['Mario', 'Luigi']);
test('the players have been rendered', async () => {
const wrapper = mount(HelloWorld);
const lastPlayer = await wrapper.findAll('.player');
expect(lastPlayer).toHaveLength(2);
});
My component script:
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const apiMessage = ref('');
const players = ref<string[]>([]);
onMounted(async () => {
const fetchMessage = fetch('api/welcome-message')
.then((res) => res.text())
.then((message: string) => (apiMessage.value = message));
const fetchPlayers = fetch('api/players')
.then((res) => res.json())
.then((playersRes: string[]) => (players.value = playersRes));
});
</script>
The test fails because, I assume, the code running in onMounted doesn't have time to complete before the test looks for all .player <li> elements (rendered with a v-for) off of the players ref. How can I ask vitest to wait for the responses from each of these fetches before calling the test a failure.
Thanks.
The fetch Promises resolve in the next macro tick, which can be awaited like this:
test('...', async() => {
⋮
await new Promise(r => setTimeout(r));
})
Or you can use Vue Test Utils' utility for this:
import { flushPromises } from '#vue/test-utils';
test('...', async() => {
⋮
await flushPromises();
})
Add that line before running any assertions:
👇
import { mount, flushPromises } from '#vue/test-utils';
import HelloWorld from './HelloWorld.vue';
import { mockGet } from 'vi-fetch';
import 'vi-fetch/setup';
mockGet('api/welcome-message').willResolve('Welcome message from vitest');
mockGet('api/players').willResolve(['Mario', 'Luigi']);
test('the players have been rendered', async () => {
const wrapper = mount(HelloWorld);
👇
await flushPromises();
const lastPlayer = await wrapper.findAll('.player');
expect(lastPlayer).toHaveLength(2);
});
demo
Related
I have this test
import { mount, RouterLinkStub, createLocalVue } from '#vue/test-utils'
import VueMask from 'v-mask'
import Vuex from 'vuex'
import RcLoginForm from '#/components/auth/login-form'
import authorization from '#/store/authorization'
const axios = require('axios')
const Login = require('./Login')
jest.mock('axios')
describe('RcLoginForm', () => {
let store
let wrapper
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueMask)
beforeEach(() => {
store = new Vuex.Store({
namespaced: true,
...authorization
})
})
wrapper = mount(RcLoginForm, {
localVue,
store,
stubs: {
NuxtLink: RouterLinkStub,
},
})
and this function on my component
methods: {
...mapActions('authorization', ['loginHandler']),
async submit() {
this.fetching = true
const result = await this.loginHandler({
username: this.username,
I'm calling the login function by triggering event on button component
await wrapper.find('button').trigger('click')
get this error
[vuex] module namespace not found in mapActions(): authorization/
Okey, I have some test method and lifecycle hook
testMethod(){
console.log('test')
}
beforeMount() {
this.testMethod()
},
test
import {createLocalVue, shallowMount} from "#vue/test-utils"
import TestComponents from '../../assets/components/TestComponents'
const localVue = createLocalVue()
describe('test component TestComponents', () => {
let wrapper = shallowMount(TestComponents,
{
localVue
})
test('beforeMount hook', async () => {
let testMethodSpy = jest.spyOn(wrapper.vm, 'testMethod')
expect(testMethodSpy).toBeCalled()
})
})
Why is the method called(in command line "console.log test") but the validation returns an error?
enter image description here
enter image description here
I want to use fresh Vuex store in every test, so I'm looking to replace default store with test store. Reason is that my Router is using store getter to check if user is allowed to access specific route.
The problem is that it always uses default store, which is not used by test component. I've tried to mock #\store, but looks like I'm missing something.
#\store\index.ts
import {createStore, Store} from 'vuex'
import {State} from '#vue/runtime-core'
import auth from "#/store/modules/auth";
export const createNewStore = (): Store<State> => (createStore({
modules: {
auth,
},
}))
export default createNewStore()
#\router\index.ts
import {createMemoryHistory, createRouter, createWebHistory, Router, RouteRecordRaw} from 'vue-router'
import store from '#/store';
const routes: Array<RouteRecordRaw> = [ ... ]
export const createNewRouter = (): Router => {
const isServer = Boolean(typeof module !== 'undefined' && module.exports)
const router = createRouter({
history: isServer ? createMemoryHistory() : createWebHistory(process.env.BASE_URL),
routes
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => !record.meta.doesntRequiresAuth) && !store.getters['auth/isAuthenticated']) {
next({name: 'login'})
} else {
next()
}
})
return router
}
export default createNewRouter()
Current implementation looks like this:
#\views\__tests__\Login.spec.ts
import {render, screen} from '#testing-library/vue';
import AppComponent from '../../App.vue';
import userEvent from "#testing-library/user-event";
import {createNewRouter} from "#/router";
const {createNewStore} = jest.requireActual('#/store');
import {Router} from "vue-router";
describe('Login.vue', () => {
let router : Router
const renderComponentWithDependencies = async () => {
const mockStore = createNewStore();
jest.doMock('#/store', () => ({
__esModule: true,
default: mockStore,
}));
router = createNewRouter();
await router.push('/')
render(AppComponent, {
global: {
plugins: [router, mockStore],
}
})
}
beforeEach(async () => {
await renderComponentWithDependencies()
fetchMock.resetMocks();
});
it('User logs with correct username and pin', async () => {
const username = 'Pavel'
const pin = 'test'
fetchMock.mockResponseOnce(JSON.stringify({}));
fetchMock.mockResponseOnce(JSON.stringify({token: "123"}));
await screen.findByLabelText("Имя пользователя")
userEvent.type(screen.getByLabelText("Имя пользователя"), username)
await userEvent.click(screen.getByText('Запросить пин'))
userEvent.type(await screen.findByLabelText('Пин код'), pin)
await userEvent.click(screen.getByText('Войти'))
await router.isReady()
await screen.findByText('You are on home page')
})
})
Turns out there is way better solution I wasn't aware of, which doesn't require mocking - use jest.resetModules():
import {render, screen} from '#testing-library/vue';
import AppComponent from '../../App.vue';
import userEvent from "#testing-library/user-event";
import router from "#/router";
import store from '#/store';
describe('Login.vue', () => {
const renderComponentWithDependencies = async () => {
await router.push('/')
// We use App component instead of Login to test routing to homepage at the end of the login
render(AppComponent, {
global: {
plugins: [router, store],
}
})
}
beforeEach(async () => {
await renderComponentWithDependencies()
fetchMock.resetMocks();
jest.resetModules();
});
it('User logs with correct username and pin', async () => {
const username = 'Pavel'
const pin = 'test'
fetchMock.mockResponseOnce(JSON.stringify({}));
fetchMock.mockResponseOnce(JSON.stringify({token: "123"}));
await screen.findByLabelText("Имя пользователя")
userEvent.type(screen.getByLabelText("Имя пользователя"), username)
await userEvent.click(screen.getByText('Запросить пин'))
userEvent.type(await screen.findByLabelText('Пин код'), pin)
await userEvent.click(screen.getByText('Войти'))
await router.isReady()
await screen.findByText('You are on home page')
})
})
Can anyone tell me how to write a test case for the functions which are in onMounted hook? I am using vue 2 with composition api plugin.
const getUsers = async () => {
const usersQuery = `
query {
users: {
id
username
}
}
`
try {
const result = await apolloClient.getGraphqlData(usersQuery)
if (result) users.value = result.data.users
} catch (err) {
console.log('Error while receiving users', err)
}
}
Below is my onMounted hook
onMounted(() => {
getUsers()
})
You can't mock local functions defined in setup(). The functions have to be exposed so that the unit tests can access them. One workaround is to declare the methods in an external file, adjacent to the component:
// mylib.js
export const getUsers = async () => { /*...*/ }
And import that file in your component:
import { onMounted } from '#vue/composition-api'
import { getUsers } from './mylib'
export default {
setup() {
onMounted(() => getUsers())
}
}
Then in your test file, import the same file, and use jest.mock() to auto-mock it, which would allow you to verify that the function was called upon the component mounting:
import { getUsers } from '#/components/mylib'
import MyComponent from '#/components/MyComponent.vue'
jest.mock('#/components/setupFns')
describe('MyComponent', () => {
beforeEach(() => jest.resetAllMocks())
it('calls getUsers() on mount', () => {
shallowMount(MyComponent)
expect(getUsers).toHaveBeenCalledTimes(1)
})
})
demo
Function:
(Update: Because of the other code I need it to be async/await, otherwise my other code doesn't work)
async getAll() {
request_url = "http://localhost:8082/test"
await axios
.get(request_url, {
headers: { 'Access-Control-Allow-Origin': '*' },
})
.then(response => {
this.all= response.data
})
.catch(error => {
this.errorMessage = error.message
})
},
Test:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import Map from '#/views/Map'
import Vue from 'vue'
import Vuetify from 'vuetify'
import moxios from 'moxios'
import axios from 'axios'
import flushPromises from 'flush-promises'
jest.mock('axios');
Vue.use(Vuetify)
let vuetify
let wrapper
vuetify = new Vuetify()
const localVue = createLocalVue()
describe('View', () => {
beforeEach(() => {
jest.clearAllMocks();
moxios.install()
wrapper = shallowMount(Map, {
localVue,
vuetify,
})
// I need this because I call another function with axios on mounted
flushPromises()
})
it('should get all', async () => {
axios.get = jest.fn().mockResolvedValue({
data: [
{
test: 'test'
}
]
});
await wrapper.vm.getAll().then(() => {
expect(wrapper.vm.all).toEqual(
test: 'test'
)
})
})
Result:
Expected value to equal:
{"test": "test"}
Received:
undefined
I've tried with promises, with moxios, with sinon, nothing seems to work. When I log the this.all in the function, it has the correct value. However, in the test it doesn't wait for the function to finish and for the value to be assigned to this.all. I tried with nextTick, with flushPromises, with await. How do I make the test wait until the function getAll() has assigned this.all the response data? Thank you in advance.
Your method getAll() should be a promise, because "axios" is and returns a promise.
So.. if getAll is a promise, you can call it next with .then().catch()..
Remember return in your promise the resolve() when everyting the tasks ends, or rejection() if your promise ends with errors.