I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js
which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js
which in turn uses axios
to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url
is called, so want to spy on, or mock, client
.
I have followed various examples and posts, but struggling to work out how I mock an import (client
) of an import (users.js
).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '@/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js
file.
I can't seem to mock client
explicitly because the import
statements run first, and so client
is imported inside users.js
before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe
):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
CodePudding user response:
Mocking a module
vi.mock()
's path argument needs to resolve to the same file that the module under test is using. If users.js
imports <root>/src/client.js
, vi.mock()
's path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test()
:
- Dynamically import the module, which gets the mocked module.
- Mock the function off of the mocked module reference, optionally returning a mock value. Since
client.get()
returnsaxios.get()
, which returns aPromise
, it makes sense to usemockResolvedValue()
to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '@/users.js'
vi.mock('@/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('@/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})