Search code examples
javascriptvue.jsunit-testingecmascript-6vitest

Spying On/Mocking Import of an Import


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

Solution

  • Late to the party but just in case anyone else is facing the same issue.

    I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.

    import { client } from 'client';
    
    vi.mock('client', () => {
        const client = vi.fn();
        client.get = vi.fn();
        
        return { client }
    });
    
    

    Then in those tests calling client.get() behind the scenes as a dependency, just add

      client.get.mockResolvedValue({fakeResponse: []});
    

    and the mocked function will be called instead of the real implementation.

    If you are using a default export, look at the vitest docs since you need to provide a default key.

    If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.