N
Nuxt9mo ago
miracoly

Mocking Pinia Store in Vitest Component Test

I would like to test my Nuxt3 component with vitest and nuxt/test-utils. This component uses a Pinia store. Component (simplified):
<template>
<Foo :leagues="allLeagues" />
</template>
<script setup lang="ts">
const leagueStore = useLeagueStore();
const { retrieveAllLeagues } = leagueStore;
const { allLeagues } = storeToRefs(leagueStore);

await callOnce(retrieveAllLeagues);
</script>
<template>
<Foo :leagues="allLeagues" />
</template>
<script setup lang="ts">
const leagueStore = useLeagueStore();
const { retrieveAllLeagues } = leagueStore;
const { allLeagues } = storeToRefs(leagueStore);

await callOnce(retrieveAllLeagues);
</script>
Store (simplified):
import { useBackend } from '~/composable/useBackend';
import { type League } from '~/utils/models/league';
import type { ComputedRef } from 'vue';

export const useLeagueStore = defineStore('leagueStore', () => {
const allLeagues = ref<League[]>();

const retrieveAllLeagues = async (): Promise<void> => {
const { data } = await useBackend<League[]>('/v1/leagues');
allLeagues.value = data.value ?? undefined;
};

return {
allLeagues,
retrieveAllLeagues,
};
});
import { useBackend } from '~/composable/useBackend';
import { type League } from '~/utils/models/league';
import type { ComputedRef } from 'vue';

export const useLeagueStore = defineStore('leagueStore', () => {
const allLeagues = ref<League[]>();

const retrieveAllLeagues = async (): Promise<void> => {
const { data } = await useBackend<League[]>('/v1/leagues');
allLeagues.value = data.value ?? undefined;
};

return {
allLeagues,
retrieveAllLeagues,
};
});
I've now tried to test it as described on the Pina page about testing. Test:
import { describe, expect, test } from 'vitest';
import { renderSuspended } from '@nuxt/test-utils/runtime';
import { screen, within } from '@testing-library/vue';
import { createTestingPinia } from '@pinia/testing';

describe('foo', () => {
const { _1, _2, _3 } = leagues;

test('bar', async () => {
const testingPinia = createTestingPinia();
const store = useLeagueStore(testingPinia);
store.allLeagues = [_1, _2, _3];
await renderSuspended(DashboardPage, {
global: { plugins: [testingPinia] },
});

expect(store.retrieveAllLeagues).toHaveBeenCalledOnce();

[_1, _2, _3].forEach(league => {
expect(screen.getByText(league.title)).toBeVisible();
});
});
});
import { describe, expect, test } from 'vitest';
import { renderSuspended } from '@nuxt/test-utils/runtime';
import { screen, within } from '@testing-library/vue';
import { createTestingPinia } from '@pinia/testing';

describe('foo', () => {
const { _1, _2, _3 } = leagues;

test('bar', async () => {
const testingPinia = createTestingPinia();
const store = useLeagueStore(testingPinia);
store.allLeagues = [_1, _2, _3];
await renderSuspended(DashboardPage, {
global: { plugins: [testingPinia] },
});

expect(store.retrieveAllLeagues).toHaveBeenCalledOnce();

[_1, _2, _3].forEach(league => {
expect(screen.getByText(league.title)).toBeVisible();
});
});
});
Both assertions fail, the first one with AssertionError: expected "spy" to be called once, but got 0 times. Its like my component does not pick up the testing Pinia instance for some reason.
1 Reply
miracoly
miracolyOP8mo ago
Thanks for the idea! I was able to mock it with mockNuxtImport() and vitest's mock functionality.
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { mockNuxtImport, renderSuspended } from '@nuxt/test-utils/runtime';
import { screen } from '@testing-library/vue';

const { useLeagueStoreMock, retrieveAllLeagues } = vi.hoisted(
() => ({
useLeagueStoreMock: vi.fn<any, Partial<LeagueStoreTree>>(),
retrieveAllLeagues: vi.fn(),
}),
);

retrieveAllLeagues.mockImplementation(async (): Promise<void> => {
await Promise.resolve();
});

mockNuxtImport('useLeagueStore', () => useLeagueStoreMock);

beforeEach(() => {
useLeagueStoreMock.mockRestore();
});

describe('foo', () => {
beforeEach(async () => {
useLeagueStoreMock.mockImplementation(() => ({
allLeagues: computed(() => [...]),
retrieveAllLeagues,
}));
});

test('initializes allLeagues state', async () => {
await renderSuspended(DashboardPage);
...
});
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { mockNuxtImport, renderSuspended } from '@nuxt/test-utils/runtime';
import { screen } from '@testing-library/vue';

const { useLeagueStoreMock, retrieveAllLeagues } = vi.hoisted(
() => ({
useLeagueStoreMock: vi.fn<any, Partial<LeagueStoreTree>>(),
retrieveAllLeagues: vi.fn(),
}),
);

retrieveAllLeagues.mockImplementation(async (): Promise<void> => {
await Promise.resolve();
});

mockNuxtImport('useLeagueStore', () => useLeagueStoreMock);

beforeEach(() => {
useLeagueStoreMock.mockRestore();
});

describe('foo', () => {
beforeEach(async () => {
useLeagueStoreMock.mockImplementation(() => ({
allLeagues: computed(() => [...]),
retrieveAllLeagues,
}));
});

test('initializes allLeagues state', async () => {
await renderSuspended(DashboardPage);
...
});
That does the trick. In my component, I utilize callOnce, and it appears to truly execute only once within a single test file. That's why retrieveAllLeagues isn't restored with mockRestore(). I suppose if someone wants to ensure the correct methods are called with callOnce, they either need to assert them in the proper sequence or employ multiple test files.
Want results from more Discord servers?
Add your server