Home > OS >  Spying On/Mocking Import of an Import
Spying On/Mocking Import of an Import

Time:05-19

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():

  1. Dynamically import the module, which gets the mocked module.
  2. Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() 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)
  })
})

demo

  • Related