vitest-dev / vitest

Next generation testing framework powered by Vite.
https://vitest.dev
MIT License
13.05k stars 1.17k forks source link

Fails to mock module from lib if no `"main"` entry in `package.json` #4029

Open ivan-lednev opened 1 year ago

ivan-lednev commented 1 year ago

Describe the bug

I'm developing a plugin for an Electron app. The app provides type definitions to plugin devs in a library with the following package.json: https://github.com/obsidianmd/obsidian-api/blob/master/package.json

So in my code I import stuff like this:

import { parseYaml } from "obsidian";

I bundle the plugin, and the application consumes that bundle, injecting "obsidian" as an external dependency (which is not present at plugin build-time).

Once I try to mock "obsidian" either with a factory method or with __mocks__, Vitest gives me grief:

Error: Failed to resolve entry for package "obsidian". The package may have incorrect main/module/exports specified in its package.json.
 ❯ packageEntryFailure node_modules/vite/dist/node/chunks/dep-df561101.js:28691:11
 ❯ resolvePackageEntry node_modules/vite/dist/node/chunks/dep-df561101.js:28688:5
 ❯ tryNodeResolve node_modules/vite/dist/node/chunks/dep-df561101.js:28419:20
 ❯ Context.resolveId node_modules/vite/dist/node/chunks/dep-df561101.js:28180:28
 ❯ Object.resolveId node_modules/vite/dist/node/chunks/dep-df561101.js:44207:32
 ❯ MessagePort.<anonymous> node_modules/vitest/dist/vendor-index.b271ebe4.js:59:20

And indeed, once I manually add a dummy main.js and update package.json in the library with "main": "./main.js", Vitest is happy.

Reproduction

  1. Install the library: npm i obsidian
  2. Create a test file
  3. Add and run the following test:
    
    import { parseYaml } from "obsidian";

vi.mock("obsidian", () => { return { parseYaml() { return "result" } }; });

test("mocked method works", () => { expect(parseYaml("")).toEqual("result"); });


### System Info

```shell
System:
    OS: Windows 10 10.0.22621
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
    Memory: 9.56 GB / 31.50 GB
  Binaries:
    Node: 18.16.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 9.5.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.22621.2134.0), Chromium (116.0.1938.54)
    Internet Explorer: 11.0.22621.1
  npmPackages:
    vite: ^4.4.9 => 4.4.9
    vitest: ^0.34.3 => 0.34.3

Used Package Manager

npm

Validations

sheremet-va commented 1 year ago

Does it work with Vite? We just call their resolver, there is no additional logic for this.

madeleineostoja commented 1 year ago

I'm also facing a similar issue to this — in my case I'm mocking a module that isn't available in a testing environment, and vitest is complaining that it can't resolve it even when it's mocked. I'm pretty fresh to vitest but coming from jest I could mock just about anything regardless of what module resolution is happening or not in the background, is that not the case with vitest?

srg-kostyrko commented 11 months ago

I faced same problem when testing my obsidian plugin with vitest. After a bit of experimenting I was able to find a workaround. I created a file containing mocked parts of obsidian API that I needed (in my case it was just moment library) and configured alias in vitest config to that file

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    alias: {
      obsidian: './__mocks__/obsidian.ts'
    }
  },
})

With this vitest stops reading obsidian package (that actually just types) and there is no longer resolve error.

MrGVSV commented 10 months ago

Does it work with Vite? We just call their resolver, there is no additional logic for this.

@sheremet-va Not sure about OP's case, but for vite itself I don't run into this issue so long as I include the following in my vite.config.ts:

export default defineConfig(() => ({
  // ...
  build: {
    // ...
    rollupOptions: {
      external: ['obsidian'],
    },
  },
});

But this does not seem to fix the issue for vitest.

MrGVSV commented 10 months ago

I faced same problem when testing my obsidian plugin with vitest. After a bit of experimenting I was able to find a workaround. I created a file containing mocked parts of obsidian API that I needed (in my case it was just moment library) and configured alias in vitest config to that file

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    alias: {
      obsidian: './__mocks__/obsidian.ts'
    }
  },
})

With this vitest stops reading obsidian package (that actually just types) and there is no longer resolve error.

This worked for me, but since the alias is relative to the file importing it, I had to specify it like:

// vitest.config.ts
import path from 'path';

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    alias: {
      obsidian: path.resolve(__dirname, "__mocks__/obsidian.ts"),
    }
  },
})