How to mock svelte/store with vitest

643 Views Asked by At

I'm new to svelte and I'm wondering how to correctly mock the svelte/store with predefined state.

I've got this kind of approach and it's actually working, but I'm not sure if this is good way to do this.

This is my example.svelte

<script lang="ts">
    import { userIds } from './store'

    function storeHandler(ids: Set<string>) {
        // some logic with the store values
        return ids
    }
    $: result = storeHandler($userIds)
</script>

{#each result as item}
    some funky stuff 
{/each}

store.ts

import { writable } from 'svelte/store'


export const userIds = writable<Set<string>>(new Set())

And finally my example.test.ts :

it('some test case', async () => {
        userIds.set(['612e8978c1af93207fc97808'])
        render(Example)

        //some test actions that are actually passing correctly

        userIds.set([])
    })

P.S the question is more of mocking the store that is not passed to components as a prop, but just imported directly inside.

1

There are 1 best solutions below

0
Gil Cohen On

After finding a solution, I'm starting to think that it might not be the best to mock your stores, when they're more complicated than just a basic writeable, like mine.

I expose an initStore function from my stores, and I think that should be mocked, and the rest shouldn't be mocked.

My stores can be found in the $lib folder.

@mocks point to a general mock folder (__mocks__), and inside it I mocked the categories store, to be used in various test files.

Here is an option to mock only the initialization:

vi.mock('$lib/categories', async () => {
  const { map } = await import('@mocks/$lib/categories'); // you could mock it inside this factory
  const actual =
    await vi.importActual<typeof import('$lib/categories')>('$lib/categories');
  actual.categoriesStore.initStore([...map.values()]);
  return actual;
});

For this solution, you would have to add the following alias to your vitest.config.ts:

{ find: '$lib', replacement: resolve(__dirname, './src/lib') }

Anyway, here is my solution to mock a whole store:

One way to do it is thus:

vi.mock('$lib/categories', async () => {
  const { categoriesStore } = await import('@mocks/$lib/categories');
  return { categoriesStore };
});

According to vitest's mock's documentation, having the mock inside __mocks__ and calling vi.mock('$lib/categories'); inside your test file should call the mock, but it's not working for $lib.

One way to solve it more globally, is defining your vitest.config.ts thus:

import { dirname, resolve } from 'node:path';
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { fileURLToPath } from 'node:url';

export default defineConfig({
  plugins: [svelte({ hot: !process.env.VITEST })],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: 'vitest-setup.ts',
    include: ['src/**/*.test.ts'],
    alias: [
      {
        find: '$app/forms',
        replacement: resolve(
          fileURLToPath(dirname(import.meta.url)),
          './src/__mocks__/$app/forms.ts',
        ),
      },
      {
        find: '$app/environment',
        replacement: resolve(
          fileURLToPath(dirname(import.meta.url)),
          './src/__mocks__/$app/environment.ts',
        ),
      },
      {
        find: '$lib',
        replacement: resolve(
          fileURLToPath(dirname(import.meta.url)),
          './src/__mocks__/$lib/index.ts',
        ),
      },
    ],
  },
});

Then, export your stores from the index.ts file.

BTW, I added my aliases for Svelte's $app, I thought it might be useful as well (took me a while to nail that down).