vitest
vitest copied to clipboard
vue-router's useRoute is not correctly mocked
Describe the bug
When using vitest-suggested testing libraries, I can't successfully mock useRoute
and useRouter
from the vue-router
package.
I'm using:
- jsdom
- @testing-library/vue
Any time I run a component test with a mocked useRoute
, the mock returns undefined, and Vue warns about missing injections.
...
[Vue warn]: injection "Symbol(route location)" not found.
at <HelloWorld ref="VTU_COMPONENT" >
at <VTUROOT>
...
TypeError: Cannot read properties of undefined (reading 'path')
19|
20| <div class="card">
21| <button type="button" @click="count++">count is {{ count }}</button>
| ^
22| <p>
23| Edit
Reproduction
See a minimal reproduction, using a fresh yarn create vite
project here: https://github.com/hidde-jan/vitest-use-route-example/blob/main/src/components/test/HelloWorld.test.ts
System Info
System:
OS: macOS 12.3.1
CPU: (10) arm64 Apple M1 Pro
Memory: 76.22 MB / 32.00 GB
Shell: 3.3.1 - /opt/homebrew/bin/fish
Binaries:
Node: 16.14.0 - ~/.nodenv/versions/16.14.0/bin/node
Yarn: 1.22.15 - ~/.nodenv/versions/16.14.0/bin/yarn
npm: 8.3.1 - ~/.nodenv/versions/16.14.0/bin/npm
Browsers:
Chrome: 104.0.5112.101
Firefox: 102.0.1
Safari: 15.4
npmPackages:
@vitejs/plugin-vue: ^3.0.3 => 3.0.3
vite: ^3.0.7 => 3.0.9
vitest: ^0.22.1 => 0.22.1
### Used Package Manager
yarn
### Validations
- [X] Follow our [Code of Conduct](https://github.com/vitest-dev/vitest/blob/main/CODE_OF_CONDUCT.md)
- [X] Read the [Contributing Guidelines](https://github.com/vitest-dev/vitest/blob/main/CONTRIBUTING.md).
- [X] Read the [docs](https://vitest.dev/guide/).
- [X] Check that there isn't [already an issue](https://github.com/vitest-dev/vitest/issues) that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/vitest-dev/vitest/discussions) or join our [Discord Chat Server](https://chat.vitest.dev).
- [X] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
Should be fixed by https://github.com/vitest-dev/vitest/issues/1919 and https://github.com/vitejs/vite/pull/9860
For now, you can manually add node
condition to your config:
{
resolve: {
conditions: process.env.VITEST ? ['node'] : []
}
}
Thanks. I can confirm this actually makes the mock work.
Any specific reason why this bug occurs? It has something to do with module resolution?
Verstuurd vanaf mijn iPhone
Op 26 aug. 2022 om 08:59 heeft Vladimir @.***> het volgende geschreven:
Should be fixed by #1918 and vitejs/vite#9860
For now, you can manually add node condition to your config:
{ resolve: { conditions: process.env.VITEST ? ['node'] : [] } } — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.
Thanks. I can confirm this actually makes the mock work. Any specific reason why this bug occurs? It has something to do with module resolution?
Yes. Vite uses browser
field, if vue router is imported from Vue file. But doesn't use it, if it was imported from .ts
filed (or mocked inside .ts
file). There is also an open issues in router repo: https://github.com/vuejs/router/issues/1466
For me this workaround doesn't work. Current Versions: @vitejs/plugin-vue: 3.1.0 vite: 3.1.3 vitest: 0.23.4
Should be fixed in the next Vitest version. Also requires Vite 3.2.
Should be fixed in the next Vitest version. Also requires Vite 3.2.
I'm running vitest 0.24.4 and vite 3.2.2, and yet the mock is not working. Is the issue really solved?
Mocking with fn() gives "Cannot read properties of undefined (reading 'meta')" error when reading "route.meta.breadcrumbs":
vi.mock("vue-router", () => ({ useRoute: vi.fn(() => ({ name: "organisation", query: { org: "" }, path: "/organisations/100047", meta: { breadcrumbs: [ { text: "Home", ref: "/" }, { text: "Search", ref: "/organisation-search" }, { text: "", ref: "/organisations/100047" }, ], }, })), }));
Mocking without fn() works in first test, but overrides mock definition in the next test executed:
vi.mock("vue-router", () => ({ useRoute: () => ({ name: "organisation", query: { org: "" }, path: "/organisations/100047", meta: { breadcrumbs: [ { text: "Home", ref: "/" }, { text: "Search", ref: "/organisation-search" }, { text: "", ref: "/organisations/100047" }, ], }, }), }));
Yes, it is fixed. If it wasn't, your mocks would never be called. You have error in your test code.
Yes, it is fixed. If it wasn't, your mocks would never be called. You have error in your test code.
I have tried all sorts of cleaning/restoring functions before and after each test, but they don't do anything. Mock from the first test overrides the mock definition in the next test executed. At this point, we have to put each test in a separated file to make it work, which is very messy. Is it per design that mocking router once in the file, overrides all later router mocks? Any pointers would be very helpful.
I have tried all sorts of cleaning/restoring functions before and after each test, but they don't do anything. Mock from the first test overrides the mock definition in the next test executed. At this point, we have to put each test in a separated file to make it work, which is very messy. Is it per design that mocking router once in the file, overrides all later router mocks? Any pointers would be very helpful.
vi.mock
calls are hoisted, it is mention in the documentation. There is a cheat sheet in docs that might be useful: https://vitest.dev/guide/mocking.html#cheat-sheet
The easiest way to mock a route for different tests would be, in my opinion:
import * as routerExports from 'vue-router'
const useRouteMock = vi.spyOn(routerExports, 'useRoute')
useRouteMock.mockReturnValue({ name: 'name1' })
useRouteMock.mockReturnValue({ name: 'name2' })
Or if you like vi.mock
so much, you can mark is as a spy in vi.mock
:
import { useRoute } from 'vue-router'
vi.mock('vue-router', () => ({ useRoute: vi.fn() }))
vi.mocked(useRoute).mockReturnValue({ name: 'name1' })
vi.mocked(useRoute).mockReturnValue({ name: 'name2' })
I have tried all sorts of cleaning/restoring functions before and after each test, but they don't do anything. Mock from the first test overrides the mock definition in the next test executed. At this point, we have to put each test in a separated file to make it work, which is very messy. Is it per design that mocking router once in the file, overrides all later router mocks? Any pointers would be very helpful.
vi.mock
calls are hoisted, it is mention in the documentation. There is a cheat sheet in docs that might be useful: https://vitest.dev/guide/mocking.html#cheat-sheetThe easiest way to mock a route for different tests would be, in my opinion:
import * as routerExports from 'vue-router' const useRouteMock = vi.spyOn(routerExports, 'useRoute') useRouteMock.mockReturnValue({ name: 'name1' }) useRouteMock.mockReturnValue({ name: 'name2' })
Or if you like
vi.mock
so much, you can mark is as a spy invi.mock
:import { useRoute } from 'vue-router' vi.mock('vue-router', () => ({ useRoute: vi.fn() })) vi.mocked(useRoute).mockReturnValue({ name: 'name1' }) vi.mocked(useRoute).mockReturnValue({ name: 'name2' })
Hello again, I've tried both of your examples, and nothing works.
import * as routerExports from "vue-router";
vi.spyOn(routerExports, "useRoute").mockReturnValue({
name: "organisation",
query: { org: "" },
path: "/organisations/100047",
meta: {
breadcrumbs: [
{ text: "Home", ref: "/" },
{ text: "Search", ref: "/organisation-search" },
{ text: "", ref: "/organisations/100047" },
],
},
matched: [],
fullPath: "",
hash: "",
redirectedFrom: undefined,
params: {},
});
This is the error I'm getting:
TypeError: Cannot assign to read only property 'useRoute' of object '[object Module]'
❯ src/components/Breadcrumbs/Breadcrumbs.test.ts:31:7
29| const wrapper = breadcrumbsFactory();
30|
31| vi.spyOn(routerExports, "useRoute").mockReturnValue({
| ^
32| name: "organisation",
33| query: { org: "" },
[Vue warn]: injection "Symbol(route location)" not found.
at <Breadcrumbs ref="VTU_COMPONENT" >
at <VTUROOT>
Please reopen this case, the bug hasn't been fixed. @sheremet-va
Still getting:
[Vue warn]: injection "Symbol(route location)" not found.
at <XenaBreadcrumbs ref="VTU_COMPONENT" >
at <VTUROOT>
[Vue warn]: Unhandled error during execution of render function
at <XenaBreadcrumbs ref="VTU_COMPONENT" >
at <VTUROOT>
TypeError: Cannot read properties of undefined (reading 'meta')
❯ ReactiveEffect.fn src/components/Breadcrumbs/XenaBreadcrumbs.vue:17:19
15| const route = useRoute();
16| console.log(route);
17| const br = route.meta.breadcrumbs as JSON;
| ^
18|
19| if (route.name === "organisation") {
import * as routerExports from "vue-router";
const mockedUseRoute = {
name: "organisation",
query: { org: "" },
path: "/organisations/100047",
meta: {
breadcrumbs: [
{ text: "Home", ref: "/" },
{ text: "Search", ref: "/organisation-search" },
{ text: "", ref: "/organisations/100047" },
],
},
matched: [],
fullPath: "",
hash: "",
redirectedFrom: undefined,
params: {},
};
const useRouteMock = vi
.spyOn(routerExports, "useRoute")
.mockImplementation(() => mockedUseRoute);
Same issue. vitest version: ^0.24.5
Updated: I did manage to make it work with vi.mock This is what i did for anyone using quasar:
- I was using vitest 0.15.0 so i updated it to the latest 0.26.3
- I added an override as stated on @quasar/quasar-app-extension-testing-unit-vitest:
"overrides": {
"@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.0.3",
"vitest": "^0.26.3"
}
const mockPush = vi.fn();
vi.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' },
}),
}));
This is the error I'm getting:
TypeError: Cannot assign to read only property 'useRoute' of object '[object Module]' ❯ src/components/Breadcrumbs/Breadcrumbs.test.ts:31:7 29| const wrapper = breadcrumbsFactory(); 30| 31| vi.spyOn(routerExports, "useRoute").mockReturnValue({ | ^ 32| name: "organisation", 33| query: { org: "" }, [Vue warn]: injection "Symbol(route location)" not found. at <Breadcrumbs ref="VTU_COMPONENT" > at <VTUROOT>
I'm facing the same issue as you. I was trying to spy the useRouter() but it trows:
Cannot assign to read only property 'useRoute' of object
Also tried with vi.mock and vue-router-mock library, none of those options did work for me
I'm using: Vue 3 composition api Quasar v2
works for me with
"@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.0.3",
"vitest": "^0.28.1"
I have tried all sorts of cleaning/restoring functions before and after each test, but they don't do anything. Mock from the first test overrides the mock definition in the next test executed. At this point, we have to put each test in a separated file to make it work, which is very messy. Is it per design that mocking router once in the file, overrides all later router mocks? Any pointers would be very helpful.
vi.mock
calls are hoisted, it is mention in the documentation. There is a cheat sheet in docs that might be useful: https://vitest.dev/guide/mocking.html#cheat-sheetThe easiest way to mock a route for different tests would be, in my opinion:
import * as routerExports from 'vue-router' const useRouteMock = vi.spyOn(routerExports, 'useRoute') useRouteMock.mockReturnValue({ name: 'name1' }) useRouteMock.mockReturnValue({ name: 'name2' })
Or if you like
vi.mock
so much, you can mark is as a spy invi.mock
:import { useRoute } from 'vue-router' vi.mock('vue-router', () => ({ useRoute: vi.fn() })) vi.mocked(useRoute).mockReturnValue({ name: 'name1' }) vi.mocked(useRoute).mockReturnValue({ name: 'name2' })
I got error when I used the first solution
TypeError: Cannot redefine property: useRoute
❯ src/__tests__/components/pipelineTemplate/PipelineTemplateTable.spec.ts:11:25
9| // import { useRoute } from 'vue-router'
10| import * as routerExports from 'vue-router'
11| const useRouteMock = vi.spyOn(routerExports, 'useRoute')
| ^
12| // useRouteMock.mockReturnValue({ name: 'name1' })
13| // useRouteMock.mockReturnValue({ name: 'name2' })
I'm experiencing the same issue on version 0.31.4.
See my example of the issue:
/* vitest.config.mjs */
import { defineVitestConfig } from 'nuxt-vitest/config'
export default defineVitestConfig({
test: {
globals: true,
clearMocks: true,
restoreMocks: true,
threads: false,
testTimeout: 300000,
setupFiles: ['tests/unit.i18n.setup.ts', 'tests/unit.vuetify.setup.ts', 'tests/unit.router.setup.ts'],
deps: {
inline: ['element-plus', /@nuxt\/test-utils/],
},
ssr: {
noExternal: [/vue-i18n/, 'vuetify'],
}
}
});
/* unit.router.setup.ts */
import { vi } from 'vitest';
vi.mock('vue-router', async (importOriginal) => {
const mod: object = await importOriginal();
return {
...mod,
useRouter: vi.fn(() => ({ replace: vi.fn() })),
};
});
/* component.test.ts */
describe('v-select-locale', async () => {
const router = createRouterMock();
beforeEach(() => {
injectRouterMock(router);
});
injectRouterMock(router);
type ComponentProps = any;
type ComponentVariables = {
availableLocales: ComputedRef<SelectItem<string>[]>;
currentLocale: Ref<string>;
onChangeLocale: (locale: string) => void;
};
type ComponentWrapperType = VueWrapper<ComponentPublicInstance<ComponentProps, ComponentVariables>>;
const component: ComponentWrapperType = mount(VSelectLocale, {
global: {
stubs: ['v-list-item', 'v-select'],
},
});
it('should change the selected locale', () => {
// This works...., So the router is in the component for testing
expect(component.router).toBe(router);
// Error triggered here
component.vm.onChangeLocale('en');
});
});
error:
FAIL tests/components/v-select-locale.test.ts > v-select-locale > should change the selected locale
TypeError: Cannot read properties of undefined (reading 'replace')
❯ Proxy.onChangeLocale components/v-select-locale.vue:63:10
61| */
62| function onChangeLocale(locale: string) {
63| router.replace(switchLocalePath(locale));
| ^
64| }
65|
❯ tests/components/v-select-locale.test.ts:48:5
I would expect that the setup file would mock the vue-router imports with vi.mock
@ unit.router.setup.ts
If i'm doing something wrong I would like to know what :)
I struggled with this while working on a Nuxt 3 project using Vitest, nuxt-vitest. While tinkering around I stumbled upon a combination that works for me. Here is what worked for me and maybe it helps anyone else:
vi.mock('vue-router', () => ({
useRoute: vi.fn(() => ({
fullPath: '',
hash: '',
matched: [],
meta: {},
name: undefined,
params: {
test: '123',
},
path: '/test',
query: {},
redirectedFrom: undefined,
})),
}));
My *.spec.ts
file uses // @vitest-environment nuxt
and mounts the component with mountSuspended
from nuxt-vitest
as well as auto imports the useRoute
from vue-router
automatically.
Here is the full *.spec.ts
:
// @vitest-environment nuxt
import { describe, expect, it, vi } from 'vitest';
import { mountSuspended } from 'vitest-environment-nuxt/utils';
import Test from '../[[test]].vue';
vi.mock('vue-router', () => ({
useRoute: vi.fn(() => ({
fullPath: '',
hash: '',
matched: [],
meta: {},
name: undefined,
params: {
test: '123',
},
path: '/test',
query: {},
redirectedFrom: undefined,
})),
}));
describe('Test', async () => {
const wrapper = await mountSuspended(Test, {});
it('should render correctly', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});