Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nuxt server environment support #531

Open
Tracked by #297
danielroe opened this issue Jun 2, 2023 · 7 comments
Open
Tracked by #297

nuxt server environment support #531

danielroe opened this issue Jun 2, 2023 · 7 comments
Labels
enhancement New feature or request vitest-environment

Comments

@danielroe
Copy link
Member

We currently support running composables/components in a browser-type environment (with happy-dom, and soon jsdom). But there are use cases where it might be useful to support running tests in a hybrid server environment where things like h3 utilities and nuxt ssr utilities work.

This could definitely be classed as non-essential and experimental, but we can track it here.

@silverbackdan
Copy link

Thanks for opening this issue. There are probably cases, such as using <ClientOnly>, but in my use case it would be simply mocking process.client and process.server for a couple of unit tests.

It seems that I can't do this in simple unit tests even for those tests which are not running this Nuxt environment once the plugin is installed.

@danielroe
Copy link
Member Author

Nuxt hard codes process.client and process.server to true/false based on environment, to enable tree-shaking. (So there is no process object that can be manipulated.)

@silverbackdan
Copy link

I see, thanks for the clarification

@rubennaatje
Copy link

rubennaatje commented Jun 28, 2023

as a work around for my apicalls / nitro plugins i added all auto imported server stuff to my setup.ts as

vitest.stubGlobal("defineEventHandler", (func: any) => func);
vitest.stubGlobal("defineNitroPlugin", (e: any) => e);
//etc

Which works absolutely fine for the time being, if anyone has a better idea/way i'd love to know!

I mock my own utilities the same way and then use them in unit tests like this

(getService as MockedFunction<typeof getService>).mockReturnValue(
  {mockedServiceCall: () => true}
);

@danielroe danielroe transferred this issue from danielroe/nuxt-vitest Dec 2, 2023
@dosubot dosubot bot added the stale Issue has not had recent activity or appears to be solved. Stale issues will be automatically closed label Apr 24, 2024
@dosubot dosubot bot closed this as not planned Won't fix, can't repro, duplicate, stale May 1, 2024
@dosubot dosubot bot removed the stale Issue has not had recent activity or appears to be solved. Stale issues will be automatically closed label May 1, 2024
@danielroe danielroe reopened this May 1, 2024
@danielroe danielroe pinned this issue Jun 3, 2024
@alexcroox
Copy link

Are there any plans to make this happen still? I was hoping for my full stack Nuxt framework that I'd be able to unit test the full stack but the confusion/limited support for unit testing the server side has resulted in weeks of lost time and confusion around the matter. Even a section in the testing docs warning people away from unit testing the server/api routes would help others here.

@zoobzio
Copy link

zoobzio commented Oct 31, 2024

For unit tests on Nuxt API endpoints this problem might also be solved w/ H3 test utils that are agnostic of the Nuxt runtime & instead allow for programmatic execution of event handlers

I have been playing w/ an implementation that allows for me to:

  • mock h3 utils like defineEventHandler & automatically stub the global context
  • pass mocked H3Event data to a given handler function as a test cases

Extending this workaround, we can define a function that registers h3 mocks in the global context:

// ~/test/setup.ts
import type { H3Event } from "h3";
import { vi } from "vitest";

type Handler = (event: H3Event) => Promise<unknown>;

export function useH3TestUtils() {
  const h3 = vi.hoisted(() => ({
    defineEventHandler: vi.fn((handler: Handler) => (event: H3Event) => handler(event)),
    // TODO mock these utils: https://www.jsdocs.io/package/h3#package-index-functions 
  }); 
 
  // stub `h3` & functions individually to support auto-imports
  vi.stubGlobal("h3", h3);
  vi.stubGlobal("defineEventHandler", h3.defineEventHandler);
  // TODO stub remaing utils

  return h3;
}

This makes unit testing a given handler a breeze:

// ~/test/server/api/handler.test.ts 
import { useH3TestUtils } from "@@/test/setup";
import { describe, it, expect } from "vitest";

const { 
  defineEventHandler, 
  // TODO add mocked utils your handler uses... 
} = useH3TestUtils();

describe("my handler", async () => {
  const handler = await import("../../../server/api/handler.get.ts");

  it("is registered as an event handler", () => expect(defineEventHandler).toHaveBeenCalled());
  
  it("does something", async () => {
    const results = await handler.default({ /* custom `H3Event` here */ });
    expect(results).toBe("some expected value");
  });
});

This structure could be extended to provide a broad set of utils for H3 that enable unit tests for Nuxt API endpoints & could support future development of a simulated Nuxt server environment

Hopefully this helps someone else who hits issues running Nuxt server unit tests, if there is interest in my approach I am happy to spin this up into a POC module 😄

@cyruscollier
Copy link

I'm posting this here for anyone looking for a more comprehensive solution to unit testing Nuxt server code or doing other fully-isolated unit tests. Since I added unit tests to our Nuxt 3 project long before test-utils was ready, I had to come up with a way to test the entire stack without Nuxt in the picture at all. Here's what my vitest.config.ts looks like:

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

export default defineConfig({
  plugins: [
    Vue(),
    Components({
      dirs: ['./app/components'],
      dts: './tests/vitest-components.d.ts',
    }),
    AutoImport({
      imports: [
        'vue',
        'vitest',
        'pinia',
        {
          '~~/tests/fixtures/nuxt-stub': [
            'useRuntimeConfig',
            'useCookie',
            'useState',
            'useStorage',
            'useRouter',
            'useRoute',
            'navigateTo',
            'useNuxtApp',
          ],
        },
        {
          'graphql-tag': ['gql'],
        },
      ],
      dirs: ['./app/composables', './server/utils', './app/utils', './app/stores'],
      ignore: ['./server/utils/shared.ts'],
      dts: './tests/vitest-auto-imports.d.ts',
      vueTemplate: true,
    }),
  ],
  resolve: {
    alias: {
      '~': path.resolve(__dirname, './app'),
      '~~': path.resolve(__dirname, '.'),
    // Specific to our project: generated imports from modules pointing to existing instance of them. (Requires running nuxi dev first)
      '#graphql/schema': path.resolve(__dirname, './.nuxt/graphql-schema.mjs'),
      '#graphql/generated-resolvers': path.resolve(__dirname, './.nuxt/graphql-resolvers.ts'),
     // Specific to our project: additional overrides to override specific imports with stubbed versions. Useful for mocking external APIs and services.
      '@tn-webshare/community-api-client-typescript': path.resolve(
        __dirname,
        './server/lib/tessitura-api-node-mock.ts'
      ),
      './cache': path.resolve(__dirname, './server/lib/cache-mock.ts'),
    },
  },
  test: {
    globals: true,
    include: ['./**/tests/*.spec.ts', './tests/unit/**/*.spec.ts'],
    environmentMatchGlobs: [
      // all Vue components, Pinia Stores and other client-side or isomorphic tests use jsdom
      ['./app/stores/tests/**', 'jsdom'],
      ['./tests/unit/**', 'jsdom'],
      ['./tests/unit/components/**', 'jsdom'],
    ],
  },
})

This setup allowed us to fully test code in server handlers, Vue components, Pinia stores and composables without Nuxt at all. One key is using the Vite Components and AutoImport plugins, which properly imports any auto-imported functions or components anywhere in the codebase, just like what would happen in Nuxt. The nuxt-stub.ts file referenced in the AutoImport list contains stubbed versions of commonly used Nuxt functions. This greatly reduced our reliance on using vi.stubGlobal() everywhere.

Now that I'm trying test-utils for the first time to add some integration tests, I need to have a completely separate Vitest config file from the above, since the runtime environments are so different and the auto-importing doesn't work with defineVitestConfig().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request vitest-environment
Projects
None yet
Development

No branches or pull requests

6 participants