Testing Pinia store inside Nuxt3 with vitest throws `useRuntimeConfig` not defined
Asked Answered
B

4

7

I am testing pinia store in nuxt3 app.

Inside setup() of store I'm using useRuntimeConfig to get initial value for counter from public config variables and I got this error ReferenceError: useRuntimeConfig is not defined don't know how to solve it

// store/counter.ts

...
state: () => {
    const runtimeConfig = useRuntimeConfig()
    const count = runtimeConfig.public.count
    return {
      ...
      count
      ...
    }
  },
...

code

// store/counter.test.ts

import { fileURLToPath } from 'node:url'
import { describe, expect, it, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useCounter } from './counter'
import { setup } from '@nuxt/test-utils'

await setup({
  rootDir: fileURLToPath(new URL('../', import.meta.url)),
  server: true,
  browser: true,
})

describe('Counter Store', () => {
  beforeEach(() => {
    // creates a fresh pinia and make it active so it's automatically picked
    // up by any useStore() call without having to pass it to it:
    // `useStore(pinia)`
    setActivePinia(createPinia())
  })

  it('increments', () => {
    const counter = useCounter()
    expect(counter.n).toBe(0)
    counter.increment()
    expect(counter.n).toBe(1)
  })

  it('increments by amount', () => {
    const counter = useCounter()
    counter.increment(10)
    expect(counter.n).toBe(10)
  })
})

Belle answered 10/5, 2022 at 20:58 Comment(0)
B
12

This looks similar to an issue that I just solved today. Hopefully it helps you too:

  1. In your component, in this case the Pinia store module, add the explicit import for useRuntimeConfig, as below:

    import { useRuntimeConfig } from '#imports'

At the moment I don't have a better way to get around the package being 'undefined' aside from just manually importing it. As Nuxt3 progresses, I'm hoping to see testing become more a focus. Documentation seems to suggest importing it from '#app' but I've not been able to get it working that way and '#imports' seemed the more appropriate alias.

  1. In your vitest.config.js, add in an alias that maps the #imports package to the hidden nuxt file. An example of a vitest.config.js file is below:
export default defineConfig({
  test: {
    // other test specific plugins
  },
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './'),
      '~': path.resolve(__dirname, './'),
      '#imports': path.resolve(__dirname, './.nuxt/imports.d.ts')
    }
  }
})
  1. In your test file, use the vi.mock() function to mock the #imports package.
 vi.mock('#imports', () => {
    return {
      useRuntimeConfig() {
        return {
          public: {
            // Your public config!
          }
        }
      }
    }
  })

This allowed me to mock the runtimeConfig at a test level - hopefully it helps you too! Good luck :D

For anybody reading this wanting to achieve a similar thing in Jest, this is the GitHub discussion that helped me figure this solution out. Unfortunately the discussion I used to create this solution no longer exists. (404)

EDIT: If you need to change the mocked return value on a test-by-test basis, you can return an object defined elsewhere and change the values of that object in your test.

i.e.

    let storeMock = {
       user: {
         getUsername: 'Jane Doe',
         setUsername: vi.fn()
       }
    }

    vi.mock('#imports', () => {
        return {
           useNuxtApp: vi.fn().mockImplementation(() => ({
               $store: {
                   ...storeMock
               }
           })),
        }
    })

    // In your test
    storeMock = {
        user: {
            getUsername: 'Janet Van Dyne',
            setUsername: vi.fn()
        }
    }
Beaubeauchamp answered 30/5, 2022 at 15:22 Comment(3)
thanks for this solution @katieAdamsDev, I was on the same subject, very hard to understand how to test components that have composables auto-import in them. ;) Could you add the github link you spoke about ?Patristic
@Patristic D'oh! What a dummy haha, I've linked to the GitHub discussion in my original post now :)Beaubeauchamp
Thanks for the solution! In a case somebody is trying to set this up on pure Nitro app, use this imports path instead: ./.nitro/types/nitro-imports.d.tsAmentia
A
3

The best practice for handling these auto imported composables now seems to be the mockNuxtImport() method from @nuxt/test-utils/runtime.

Documentation available here

Brief example based on what I've been trying to achieve:

// @vitest-environment nuxt
import axios from "axios"
import { describe, it, expect } from "vitest"
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
import yourServiceOrPlugin from "../your-service-or-plugin"

mockNuxtImport("useRuntimeConfig", () => {
  return () => {
    return {
      public: {
        // ...your config here
        exampleValue: "test-of-the-unit",
      },
    }
  }
})

vi.mock("axios", () => ({
  default: {
    create: vi.fn().mockReturnThis(),
    request: vi.fn(() => Promise.resolve()),
  },
}))

describe("your-service-or-plugin", () => {
  it("should make a POST request some endpoint", async () => {
    const config = useRuntimeConfig()
    const { exampleValue } = config.public

    await yourServiceOrPlugin.makeSandwich(someData)

    expect(axios.request).toHaveBeenCalledWith({
      method: "POST",
      url: exampleValue,
      headers: {
        "content-type": "application/json",
      },
      data: {
        payloadItem: someData,
      },
    })
  })
})
Alidus answered 12/4 at 17:43 Comment(2)
Best response hereGildagildas
Finally, works perfectly! thanks a lot!Enchantment
A
2

I had the same issue, but I did not want to explicitly import the composables in the components. There is a plugin that helped me a lot called 'unplugin-auto-import/vite'.

With my vitest.conf.ts am able to mock my useRoute without importing it in the component explicitly.

vitest.config.ts

import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  root: '.',
  plugins: [
    vue(),
    AutoImport({
      /* options */
      imports: ['vue-router'],
    }),
  ],

  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './setupTests.ts',
  },
  // ... standart nuxt resolve stuff from ./.nuxt/tsconfig.json
})

VueComponent.vue

<script setup lang="ts">
const route = useRoute()
defineProps<{
  componentName?: string
}>()
</script>

<template>
  <div>
    {{ route.fullPath }}
  </div>
</template>

your tests

import VueComponent from '@/components/VueComponent.vue'
import { render, screen } from '@testing-library/vue'
import '@testing-library/jest-dom'
import { vi } from 'vitest'

describe('Default Content Component', () => {
  test('Renders Content', () => {
    render(VueComponent, {
      global: {
        mocks: {
          route: { fullPath: 'asds' },
        },
      },
    })
    // ... your assertions
  })
})
Albuminuria answered 16/8, 2022 at 13:4 Comment(0)
H
2

I used vitest globalThis as effectively the tests thought useRuntimeConfig was undefined I could set it in my test like this:

globalThis.useRuntimeConfig = () => {
  return {
    public: {
       something: 'value'
    },
  };
};

Sharing sentiments with @KatieAdamsDev, I hope that the auto importing with nuxt tests becomes a little easier. For the moment the above seems not necessarily the correct long-term solution, but I will wait for the Nuxt team to define what that is.

Hyposthenia answered 19/7, 2023 at 13:52 Comment(1)
This should be the accepted answer, thank you very much!Nyctaginaceous

© 2022 - 2024 — McMap. All rights reserved.