vitest-dev/vitest

doMock cannot mock normally in sub-file import

Closed this issue · 6 comments

Describe the bug

Problem

doMock cannot mock normally in sub-file import

File structure

Please see test not working part

src/index.ts

import userMsg from './user'

export function getUserMsg () {
  return userMsg()
}

src/user/index.ts

export default function() {
  return 'hello user'
}

test/index.spec.ts

import { describe, expect, it, vi } from 'vitest'

describe('test main', () => {

  // normal work
  it('getUserMsg mock success', async function() {
    vi.doMock('@/user/index', async () => {
      return {
        default: function() {
          return 'mock_success'
        }
      }
    })
    const { getUserMsg } = await import('@/index')
    expect(getUserMsg()).toBe('mock_success')
    vi.doUnmock('@/user/index')
  })

  // not working
  it('getUserMsg mock fail', async function() {
    vi.doMock('@/user/index', async () => {
      return {
        default: function() {
          return 'mock_fail'
        }
      }
    })
    const { getUserMsg } = await import('@/index')
    expect(getUserMsg()).toBe('mock_fail')
    vi.doUnmock('@/user/index')
  })

  // not working
  it('getUserMsg real', async function() {
    const { getUserMsg } = await import('@/index')
    expect(getUserMsg()).toBe('hello user')
  })
})

when run pnpm run test,show the following error

  ❯ test/index.spec.ts (3)
   ❯ test main (3)
     ✓ getUserMsg mock success
     × getUserMsg mock fail
     × getUserMsg real

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  test/index.spec.ts > test main > getUserMsg mock fail
AssertionError: expected 'mock_success' to be 'mock_fail' // Object.is equality
 ❯ test/index.spec.ts:29:26
     27|     })
     28|     const { getUserMsg } = await import('@/index')
     29|     expect(getUserMsg()).toBe('mock_fail')
       |                          ^
     30|     vi.doUnmock('@/user/index')
     31|   })

  - Expected   "mock_fail"
  + Received   "mock_success"

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯

 FAIL  test/index.spec.ts > test main > getUserMsg real
AssertionError: expected 'mock_success' to be 'hello user' // Object.is equality
 ❯ test/index.spec.ts:36:26
     34|   it('getUserMsg real', async function() {
     35|     const { getUserMsg } = await import('@/index')
     36|     expect(getUserMsg()).toBe('hello user')
       |                          ^
     37|   })
     38| })

  - Expected   "hello user"
  + Received   "mock_success"

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯

 Test Files  1 failed (1)
      Tests  2 failed | 1 passed (3)
   Start at  23:22:18
   Duration  463ms (transform 77ms, setup 0ms, collect 45ms, tests 20ms)

### Reproduction

https://github.com/GreenMashimaro/bug-reproduce-vitest-domock

### System Info

```shell
System:
    OS: macOS 12.2.1
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 27.91 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.11.0 - ~/.nvm/versions/node/v16.11.0/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v16.11.0/bin/yarn
    npm: 8.0.0 - ~/.nvm/versions/node/v16.11.0/bin/npm
  Browsers:
    Chrome: 111.0.5563.110
    Edge: 111.0.1661.44
    Safari: 15.3
  npmPackages:
    @vitest/coverage-c8: ^0.29.7 => 0.29.7 
    @vitest/ui: ^0.29.7 => 0.29.7 
    vitest: ^0.29.7 => 0.29.7

Used Package Manager

pnpm

Validations

Your first top level import is already evaluated with its dependencies, you cannot mock dependencies after the module is imported.

@sheremet-va Thank you for your reply. I re-updated the example, each test dynamic imports @/index, but only one doMock works fine. The other two test failed. Looks like a bug.

Because there is such a scenario, it is necessary to simulate the different return values of a certain method, but this method may be in a relatively low-level file.

New examples don't change much. @/index is evaluated only once as per ESM spec. You need to reset modules cache (vi.resetModules), if you rely on a mocked file inside another module.

@sheremet-va Thank you very much for your patience. There is still a problem. I updated the file again. But vi.resetAllMocks() has no effect.

The updated file content is as follows:

index.spec.ts

import { describe, expect, it, vi } from 'vitest'

describe('test main', () => {

  // normal work
  it('getUserMsg mock success', async function() {
    vi.resetAllMocks()
    vi.doMock('@/user/index', async () => {
      return {
        default: function() {
          return 'mock_success'
        }
      }
    })
    const { getUserMsg } = await import('@/index')
    expect(getUserMsg()).toBe('mock_success')
    vi.doUnmock('@/user/index')
  })

  // not working
  it('getUserMsg mock fail', async function() {
    vi.resetAllMocks()
    vi.doMock('@/user/index', async () => {
      return {
        default: function() {
          return 'mock_fail'
        }
      }
    })
    const { getUserMsg } = await import('@/index')
    expect(getUserMsg()).toBe('mock_fail')
    vi.doUnmock('@/user/index')
  })

  // not working
  it('getUserMsg real', async function() {
    vi.resetAllMocks()
    const { getUserMsg } = await import('@/index')
    expect(getUserMsg()).toBe('hello user')
  })
})

The minimal reproducible example:https://github.com/GreenMashimaro/bug-reproduce-vitest-domock/tree/main

vi.resetAllMocks is not vi.resetModules

I apologize for my carelessness. Now the test passes perfectly. Thank you very much for your answer.