Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/api/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,47 @@ fn.length // == 2
The custom function implementation in the types below is marked with a generic `<T>`.
:::

::: warning Class Support {#class-support}
Shorthand methods like `mockReturnValue`, `mockReturnValueOnce`, `mockResolvedValue` and others cannot be used on a mocked class. Class constructors have [unintuitive behaviour](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) regarding the return value:

```ts {2,7}
const CorrectDogClass = vi.fn(class {
constructor(public name: string) {}
})

const IncorrectDogClass = vi.fn(class {
constructor(public name: string) {
return { name }
}
})

const Marti = new CorrectDogClass('Marti')
const Newt = new IncorrectDogClass('Newt')

Marti instanceof CorrectDogClass // ✅ true
Newt instanceof IncorrectDogClass // ❌ false!
```

Even though the shapes are the same, the _return value_ from the constructor is assigned to `Newt`, which is a plain object, not an instance of a mock. Vitest guards you against this behaviour in shorthand methods (but not in `mockImplementation`!) and throws an error instead.

If you need to mock constructed instance of a class, consider using the `class` syntax with `mockImplementation` instead:

```ts
mock.mockReturnValue({ hello: () => 'world' }) // [!code --]
mock.mockImplementation(class { hello = () => 'world' }) // [!code ++]
```

If you need to test the behaviour where this is a valid use case, you can use `mockImplementation` with a `constructor`:

```ts
mock.mockImplementation(class {
constructor(name: string) {
return { name }
}
})
```
:::

## getMockImplementation

```ts
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@vite-pwa/assets-generator": "^1.0.2",
"@vite-pwa/vitepress": "^1.1.0",
"@vitejs/plugin-vue": "catalog:",
"@voidzero-dev/vitepress-theme": "^4.1.1",
"@voidzero-dev/vitepress-theme": "^4.3.0",
"https-localhost": "^4.7.1",
"tinyglobby": "catalog:",
"unocss": "catalog:",
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export default antfu(
`**/*.md/${GLOB_SRC}`,
],
rules: {
'prefer-arrow-callback': 'off',
'perfectionist/sort-imports': 'off',
'style/max-statements-per-line': 'off',
'import/newline-after-import': 'off',
Expand Down
2 changes: 1 addition & 1 deletion examples/fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"devDependencies": {
"@vitest/ui": "latest",
"fastify": "^5.6.2",
"fastify": "^5.7.1",
"supertest": "^7.2.2",
"tsx": "^4.21.0",
"vite": "latest",
Expand Down
2 changes: 1 addition & 1 deletion examples/lit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"devDependencies": {
"@vitest/browser-playwright": "latest",
"jsdom": "latest",
"playwright": "^1.57.0",
"playwright": "^1.58.0",
"vite": "latest",
"vitest": "latest"
},
Expand Down
10 changes: 5 additions & 5 deletions examples/opentelemetry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-zone": "^2.3.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.209.0",
"@opentelemetry/resources": "^2.3.0",
"@opentelemetry/sdk-node": "^0.209.0",
"@opentelemetry/sdk-trace-web": "^2.3.0",
"@opentelemetry/context-zone": "^2.5.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.211.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-node": "^0.211.0",
"@opentelemetry/sdk-trace-web": "^2.5.0",
"@vitest/browser-playwright": "latest",
"vite": "latest",
"vitest": "latest"
Expand Down
6 changes: 3 additions & 3 deletions examples/projects/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.2.8",
"@types/react": "^19.2.9",
"@vitejs/plugin-react": "^5.1.2",
"@vitest/ui": "latest",
"fastify": "^5.6.2",
"fastify": "^5.7.1",
"jsdom": "^27.4.0",
"react": "^19.2.3",
"supertest": "^7.2.2",
Expand Down
2 changes: 1 addition & 1 deletion examples/typecheck/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test:run": "vitest run"
},
"devDependencies": {
"@types/node": "^24.10.7",
"@types/node": "^24.10.9",
"@vitest/ui": "latest",
"typescript": "^5.9.3",
"vite": "latest",
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"version": "4.1.0-beta.2",
"private": true,
"packageManager": "[email protected].0",
"packageManager": "[email protected].1",
"description": "Next generation testing framework powered by Vite",
"engines": {
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
Expand Down Expand Up @@ -38,13 +38,13 @@
"test:browser:playwright": "pnpm -C test/browser run test:playwright"
},
"devDependencies": {
"@antfu/eslint-config": "^6.7.3",
"@antfu/ni": "^28.1.0",
"@playwright/test": "^1.57.0",
"@antfu/eslint-config": "^7.2.0",
"@antfu/ni": "^28.2.0",
"@playwright/test": "^1.58.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@types/node": "24.10.7",
"@types/node": "24.10.9",
"@types/ws": "catalog:",
"@vitest/browser": "workspace:*",
"@vitest/coverage-istanbul": "workspace:*",
Expand All @@ -57,7 +57,7 @@
"magic-string": "^0.30.21",
"pathe": "^2.0.3",
"premove": "^4.0.0",
"rollup": "^4.55.1",
"rollup": "^4.56.0",
"rollup-plugin-dts": "^6.3.0",
"rollup-plugin-license": "^3.6.0",
"tinyglobby": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"tinyrainbow": "catalog:"
},
"devDependencies": {
"playwright": "^1.57.0",
"playwright": "^1.58.0",
"vitest": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion packages/browser-webdriverio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"@vitest/browser": "workspace:*"
},
"devDependencies": {
"@wdio/types": "^9.20.0",
"@wdio/types": "^9.23.2",
"vitest": "workspace:*",
"webdriverio": "^9.20.0"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/coverage-istanbul/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"vitest": "workspace:*"
},
"dependencies": {
"@babel/core": "^7.23.9",
"@babel/core": "^7.28.6",
"@istanbuljs/schema": "^0.1.3",
"@jridgewell/gen-mapping": "^0.3.13",
"@jridgewell/trace-mapping": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/mocker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"@vitest/spy": "workspace:*",
"@vitest/utils": "workspace:*",
"acorn-walk": "catalog:",
"cjs-module-lexer": "^2.1.1",
"cjs-module-lexer": "^2.2.0",
"es-module-lexer": "^2.0.0",
"msw": "catalog:",
"pathe": "catalog:",
Expand Down
54 changes: 48 additions & 6 deletions packages/spy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,63 @@ export function createMockInstance(options: MockInstanceOption = {}): Mock<Proce
}

mock.mockReturnValue = function mockReturnValue(value) {
return mock.mockImplementation(() => value)
return mock.mockImplementation(function () {
if (new.target) {
throwConstructorError('mockReturnValue')
}

return value
})
}

mock.mockReturnValueOnce = function mockReturnValueOnce(value) {
return mock.mockImplementationOnce(() => value)
return mock.mockImplementationOnce(function () {
if (new.target) {
throwConstructorError('mockReturnValueOnce')
}

return value
})
}

mock.mockResolvedValue = function mockResolvedValue(value) {
return mock.mockImplementation(() => Promise.resolve(value))
return mock.mockImplementation(function () {
if (new.target) {
throwConstructorError('mockResolvedValue')
}

return Promise.resolve(value)
})
}

mock.mockResolvedValueOnce = function mockResolvedValueOnce(value) {
return mock.mockImplementationOnce(() => Promise.resolve(value))
return mock.mockImplementationOnce(function () {
if (new.target) {
throwConstructorError('mockResolvedValueOnce')
}

return Promise.resolve(value)
})
}

mock.mockRejectedValue = function mockRejectedValue(value) {
return mock.mockImplementation(() => Promise.reject(value))
return mock.mockImplementation(function () {
if (new.target) {
throwConstructorError('mockRejectedValue')
}

return Promise.reject(value)
})
}

mock.mockRejectedValueOnce = function mockRejectedValueOnce(value) {
return mock.mockImplementationOnce(() => Promise.reject(value))
return mock.mockImplementationOnce(function () {
if (new.target) {
throwConstructorError('mockRejectedValueOnce')
}

return Promise.reject(value)
})
}

mock.mockClear = function mockClear() {
Expand Down Expand Up @@ -644,6 +680,12 @@ export function resetAllMocks(): void {
REGISTERED_MOCKS.forEach(mock => mock.mockReset())
}

function throwConstructorError(shorthand: string): never {
throw new TypeError(
`Cannot use \`${shorthand}\` when called with \`new\`. Use \`mockImplementation\` with a \`class\` keyword instead. See https://vitest.dev/api/mock#class-support for more information.`,
)
}

export type {
Constructable,
MaybeMocked,
Expand Down
2 changes: 1 addition & 1 deletion packages/spy/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export type MockParameters<T extends Procedure | Constructable> = T extends Cons
? Parameters<T> : never

export type MockReturnType<T extends Procedure | Constructable> = T extends Constructable
? void
? InstanceType<T>
: T extends Procedure
? ReturnType<T> : never

Expand Down
6 changes: 3 additions & 3 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@
"d3-graph-controller": "^3.1.6",
"floating-vue": "^5.2.2",
"mime": "^4.1.0",
"rollup": "^4.55.1",
"rollup": "^4.56.0",
"splitpanes": "^4.0.4",
"typescript": "^5.9.3",
"unocss": "catalog:",
"vite": "^5.0.0",
"vite-plugin-pages": "^0.33.2",
"vitest-browser-vue": "2.0.1",
"vitest-browser-vue": "2.0.2",
"vue": "catalog:",
"vue-router": "^4.6.4",
"vue-tsc": "^3.2.2",
"vue-tsc": "^3.2.3",
"vue-virtual-scroller": "2.0.0-beta.8"
}
}
8 changes: 4 additions & 4 deletions packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
},
"devDependencies": {
"@antfu/install-pkg": "^1.1.0",
"@bomb.sh/tab": "^0.0.9",
"@bomb.sh/tab": "^0.0.11",
"@edge-runtime/vm": "^5.0.0",
"@jridgewell/trace-mapping": "catalog:",
"@opentelemetry/api": "^1.9.0",
Expand All @@ -202,17 +202,17 @@
"@types/istanbul-lib-coverage": "catalog:",
"@types/istanbul-reports": "catalog:",
"@types/jsdom": "^27.0.0",
"@types/node": "^24.10.7",
"@types/node": "^24.10.9",
"@types/picomatch": "^4.0.2",
"@types/prompts": "^2.4.9",
"@types/sinonjs__fake-timers": "^15.0.0",
"@types/sinonjs__fake-timers": "^15.0.1",
"acorn": "8.11.3",
"acorn-walk": "catalog:",
"birpc": "catalog:",
"cac": "catalog:",
"empathic": "^2.0.0",
"flatted": "catalog:",
"happy-dom": "^20.1.0",
"happy-dom": "^20.3.7",
"jsdom": "^27.4.0",
"local-pkg": "^1.1.2",
"mime": "^4.1.0",
Expand Down
Loading
Loading