Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement module mocking in browser mode #3046

Closed
6 tasks done
christian-bromann opened this issue Mar 20, 2023 · 16 comments · Fixed by #5765
Closed
6 tasks done

Implement module mocking in browser mode #3046

christian-bromann opened this issue Mar 20, 2023 · 16 comments · Fixed by #5765
Labels
enhancement New feature or request feat: browser Issues and PRs related to the browser runner

Comments

@christian-bromann
Copy link
Contributor

Describe the bug

The following test:

import { test, vi } from 'vitest'
import { v4 } from 'uuid'

vi.mock('uuid', () => ({
    v4: 'I am mocked'
}))

test('I live in the browser', () => {
    console.log('YES', window.navigator.userAgent, v4)
})

throws an error:

 FAIL  tests/browser.test.js [ tests/browser.test.js ]
TypeError: this._mocker.queueMock is not a function
 ❯ VitestUtils.mock http:/localhost:63315/node_modules/.vite/deps/chunk-DGJ3HNQR.js?v=22cddd7a:10193:18
 ❯ http:/localhost:63315/private/tmp/vitestbrowser/tests/browser.test.js?v=1679345265563:4:4

Reproduction

see above

System Info

System:
    OS: macOS 12.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 420.98 MB / 32.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 18.7.0 - ~/.nvm/versions/node/v18.7.0/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v18.7.0/bin/yarn
    npm: 8.15.0 - ~/.nvm/versions/node/v18.7.0/bin/npm
  Browsers:
    Brave Browser: 111.1.49.120
    Chrome: 111.0.5563.64
    Firefox: 109.0
    Firefox Nightly: 111.0a1
    Safari: 15.2
  npmPackages:
    @vitest/browser: ^0.29.7 => 0.29.7
    vitest: ^0.29.7 => 0.29.7

Used Package Manager

npm

Validations

@sheremet-va
Copy link
Member

Mocking in the browser is not supported yet.

@sheremet-va sheremet-va added the enhancement New feature or request label Mar 20, 2023
@christian-bromann
Copy link
Contributor Author

Mocking in the browser is not supported yet.

Yes, I remember reviewing the original PR that landed the browser runner. My suggestion then would be to throw a more meaningful error for users.

@lmiller1990
Copy link

lmiller1990 commented Mar 21, 2023

@sheremet-va have you got any ideas on how best to support browser ESM mocks? We are working on something similar at Cypress for our cypress/vite-dev-server: cypress-io/cypress#22355.

Maybe we can collaborate and solve ESM mocks in the browser for both Vitest and Cypress. We can put some time and effort into contributing to both Cypress and Vitest, and Vite/esbuild if needed, to get this moving.

@sheremet-va
Copy link
Member

sheremet-va commented Mar 22, 2023

have you got any ideas on how best to support browser ESM mocks?

Module mocking is already implemented in webdriverio, as far as I understand. I took a brief glance at the code, and the idea seems solid, but we will probably need to make some adjustments:

  • move vi.mock to the top
  • rewrite spec static imports into dynamic ones, so vi.mock is called before all imports
  • when vi.mock is called, send a request to the server asking it to wait for the factory to resolve
  • when the factory is resolved, send returned keys to the server, so we can generate a new module (see later)
  • rewrite mock imports into /@mock/\id?spec= for better caching on Vite side(?)
  • when mock is imported, generate something like
    export const name = __hiddenMockState['path']['name']`

But this is about module mocking with a factory/automock. To support stub(esmModule, 'namedExport'), we will probably need to rewrite imports into something like this:

import { name } from './some-path.js'
const { something } = await import('./some-path.js')

// ->

import * as __vite_import_1__ from './some-path.js'
const { name } = __vitestModule(__vite_import_1__)

const { something } = __vitestModule(await import('./some-path.js'))

Where __vitestModule is implemented as a module accessor.

const modulesCache = new WeakMap()
function __vitestModule(module) {
  if (!modulesCache.has(module)) {
    // so ESM module loses restrictions
    modulesCache.set(module, Object.assign({}, module))
  }
  return modulesCache.get(module)
}

This is what Vite already does for optimized dependencies and for SSR.

@lmiller1990
Copy link

Gotcha... this is pretty much in line with what I was expecting for implementing this in Vitest.

Module mocking is already implemented in webdriverio, as far as I understand.

I haven't seen how they handle this, will take a look.

I saw another non-runner specific way, using importmaps. You then just combine with something like sinon. Also looking to do some exploration around this - a non-runner specific solution sure would be nice.

@sheremet-va
Copy link
Member

a non-runner specific solution sure would be nice

It would be nice, but I don't think it's possible to make it work with the same API Vitest has, unfortunately. The general solution is not DX-friendly.

@Aslemammad Aslemammad added the feat: browser Issues and PRs related to the browser runner label Mar 23, 2023
@marpme
Copy link
Contributor

marpme commented Apr 11, 2023

To summarize it: the current workaround for mocking or spying in vitest with browser mode enabled is to use libraries like sinon.js until the official implementation is done.

or do you see any other alternatives to this?

@sheremet-va
Copy link
Member

To summarize it: the current workaround for mocking or spying in vitest with browser mode enabled is to use libraries like sinon.js until the official implementation is done.

or do you see any other alternatives to this?

vi.spyOn and vi.fn work as usual. Sinon also cannot spy on module exports (no library can).

@marpme
Copy link
Contributor

marpme commented Apr 11, 2023

vi.spyOn and vi.fn work as usual. Sinon also cannot spy on module exports (no library can).

Fair point, I was under the impression that those functionalities would also be impacted by this missing implementation. Thank you for further clarifying 🥇

@sheremet-va sheremet-va changed the title bug (browser-runner): this._mocker.queueMock is not a function Implement module mocking in browser mode Apr 11, 2023
@lmiller1990
Copy link

We looked into this more at Cypress and arrived at a similar conclusion - the whole "ESM mocking" is hard to solve generally. We will solve it for Vite and webpack via a plugins, I will link the code here once we write it, maybe we can collaborate with the Vitest team to build a general vite-plugin-esm-mocks plugin, or something to that meaning.

@Aslemammad
Copy link
Member

@lmiller1990 I believe it'd be a nice idea, so other libraries and applications can use it easily.

@kettanaito
Copy link
Contributor

kettanaito commented Jun 6, 2024

Just a quick plug here that in ESM, importing modules is fetching them via HTTP, and you can use libraries like MSW to intercept module requests and respond to them with whichever application/javascript response you want.

cc @sheremet-va

@sheremet-va
Copy link
Member

sheremet-va commented Jun 6, 2024

Just a quick plug here that in ESM, importing modules is fetching them via HTTP, and you can use libraries like MSW to intercept module requests and respond to them with whichever application/javascript response you want.

cc @sheremet-va

Hm, this might actually be easier than the current implementation 🤔 The current one also doesn't work if you import original module that imports itself somewhere in the import chain, but we can bypass that with MSW maybe 🤔

@christian-bromann
Copy link
Contributor Author

I've I would would have just known about MSW earlier in my life 😭 this is amazing!

@kettanaito
Copy link
Contributor

@sheremet-va, feel free to add me to reviewers if you ever decide to give MSW a shot in module mocking. I will be happy to help you spot mistakes and suggest improvements.

@christian-bromann, but you've discovered it now, and that all that matters! ❤️

@sheremet-va
Copy link
Member

feel free to add me to reviewers if you ever decide to give MSW a shot in module mocking. I will be happy to help you spot mistakes and suggest improvements.

The first version is already implemented (and merged) here: #5853
It is a bit hard to review without the knowledge of how internals work, but you can give it a look 😄 I did not look into how this would work with custom service workers yet.

@github-actions github-actions bot locked and limited conversation to collaborators Jun 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request feat: browser Issues and PRs related to the browser runner
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants