- if youre asserting that something DOESNT exist in the DOM, youll want to use queryBy
Watch this video for more explanation + examples of "real tests"
Watch this video for more explanation + examples of "real tests"
Jest & vitest have the same apis almost exact
Test Structure:
describe()it()/test()describe.skip()/it.skip()describe.only()/it.only()test.todo()test.concurrent()test.each()describe.each()Test Lifecycle:
beforeAll()beforeEach()afterEach()afterAll()Mocking:
jest.fn()/vi.fn()jest.spyOn()/vi.spyOn()jest.mock()/vi.mock()mockImplementation()mockReturnValue()mockResolvedValue()mockRejectedValue()Timer Mocks:
jest.useFakeTimers()/vi.useFakeTimers()jest.runAllTimers()/vi.runAllTimers()jest.advanceTimersByTime()/vi.advanceTimersByTime()Assertions (expect()):
General:
.toBe().toEqual().toStrictEqual().toBeTruthy().toBeFalsy().toBeNull().toBeUndefined().toBeDefined()Numbers:
.toBeGreaterThan().toBeGreaterThanOrEqual().toBeLessThan().toBeLessThanOrEqual().toBeCloseTo()Strings:
.toMatch().toContain().toHaveLength().toMatchSnapshot()Objects/Arrays:
.toHaveProperty().toMatchObject().toContainEqual().toHaveLength()Functions/Promises:
.toThrow().toHaveBeenCalled().toHaveBeenCalledWith().toHaveBeenCalledTimes().resolves.rejectsMounting:
mount()shallowMount()Mount Options:
attachToattrsdatapropsslotsscopedSlotsglobal
global.componentsglobal.directivesglobal.mocksglobal.provideglobal.pluginsglobal.stubsshallowWrapper Methods (Interaction):
.setData().setProps().setValue().trigger().setMethods().setChecked().setSelected()Wrapper Methods (Finding/Querying):
.find().findAll().findComponent().findAllComponents().get().getComponent().filter()Wrapper Methods (Inspection):
.attributes().classes().emitted().exists().html().isVisible().props().text().vm.element.isEmpty().isVueInstance()Cleanup/Utility:
.unmount()enableAutoUnmount()flushPromises()config.globalconfig.pluginsRouterLinkStubTESTING LIBRARY VUE SYNTAX
Rendering:
render()screencleanup()within()renderHook()Queries (for each: getBy, queryBy, findBy, getAllBy, queryAllBy, findAllBy):
ByRole()ByLabelText()ByPlaceholderText()ByText()ByDisplayValue()ByAltText()ByTitle()ByTestId()User Events:
userEvent.setup()userEvent.click()userEvent.dblClick()userEvent.type()userEvent.keyboard()userEvent.upload()userEvent.clear()userEvent.selectOptions()userEvent.deselectOptions()userEvent.tab()userEvent.hover()userEvent.unhover()Fire Events:
fireEvent.click()fireEvent.change()fireEvent.submit()fireEvent.keyDown()fireEvent.keyUp()fireEvent.keyPress()| tags | ||||
|---|---|---|---|---|
|
| Layer | Description | Example(s) |
|---|---|---|
Test Library |
Highest-level APIs for testing. Provides user-friendly utilities for rendering components, querying the DOM, and simulating user interactions. Includes syntax like screen.getByText() and assertions like toBeInTheDocument(). |
@testing-library/vue, @testing-library/dom |
Test Utils |
Framework-specific testing helpers. Provides Nuxt-specific helpers for mounting and rendering components, as well as Vue-specific utilities. Includes methods like renderSuspended() (Testing Library syntax) and mountSuspended() (Vue Test Utils syntax). |
@nuxt/test-utils, @vue/test-utils |
DOM Renderer |
Virtual DOM implementation. Simulates a DOM environment for tests to run in, enabling DOM interactions and component rendering. Supports DOM-related assertions and interactions. | happy-dom, jsdom |
Test Runner |
Test execution engine. Manages test file execution, provides assertion APIs, and defines test case syntax (e.g., it(), expect()). |
vitest, Jest |
JS Build Tool |
Code transformation layer. Handles bundling, compilation, and transformation of test files. Processes source code, manages imports, and may provide features like HMR during development. | Vite, esbuild, webpack |
JS Runtime |
Code execution environment. Provides the actual JavaScript execution environment, handling module resolution, async operations, and timers. | Node.js, Browser |
Package Manager |
Foundation layer. Handles dependency resolution, installation, and script execution. Maintains deterministic builds through lock files and provides the CLI interface for running tests and other scripts. | npm, yarn, pnpm, bun |
Package Manager (e.g., yarn)
JavaScript Runtime (e.g., Node.js)
Build Tool (e.g., Vite)
Test Runner (e.g., Vitest)
DOM Renderer (e.g., happy-dom)
Test Utils (@nuxt/test-utils)
Test Library (@testing-library/vue)
A chart to outline the setup options for testing in your Nuxt 3 + Vitest + Nuxt Test Utils + TypeScript stack, covering the four potential paths.
| Path | DOM Renderer | Test Library | Install | Setup/Config | Primary Testing Methods |
|---|---|---|---|---|---|
| Path 1 | happy-dom |
Vue Test Utils |
npm install -D @nuxt/test-utils vitest @vue/test-utils happy-dom |
In vitest.config.ts, set environment: 'happy-dom' |
Use mountSuspended() to mount components. Use @vue/test-utils methods like find(), trigger(), etc. |
| Path 2 | jsdom |
Vue Test Utils |
npm install -D @nuxt/test-utils vitest @vue/test-utils jsdom |
In vitest.config.ts, set environment: 'jsdom' |
Same as Path 1, but with jsdom for broader compatibility if needed for complex DOM scenarios. |
| Path 3 | happy-dom |
Testing Library |
npm install -D @nuxt/test-utils vitest @testing-library/vue happy-dom @testing-library/jest-dom |
In vitest.config.ts, set environment: 'happy-dom'. Import @testing-library/jest-dom for extended matchers. |
Use renderSuspended() to render components. Use @testing-library/vue methods like screen.getByText(), toBeInTheDocument(). |
| Path 4 | jsdom |
Testing Library |
npm install -D @nuxt/test-utils vitest @testing-library/vue jsdom @testing-library/jest-dom |
In vitest.config.ts, set environment: 'jsdom'. Import @testing-library/jest-dom for extended matchers. |
Same as Path 3, but with jsdom for complex DOM compatibility in user-centered tests. |
happy-dom (faster) and jsdom (more compatible with complex DOM requirements).Vue Test Utils (implementation-focused) and Testing Library (user-centric).vitest.config.ts to define the test environment.types in tsconfig.json for any custom types you need.toBeInTheDocument()) for Testing Library paths.Refer to API references for the specific testing library you’re using. Here’s a breakdown of the relevant references for each library and path in your setup:
Vue Test Utils with mountSuspended(), you’ll want to reference the Vue Test Utils API documentation.mount(), find(), findComponent(), setData(), trigger(), etc.Testing Library with renderSuspended(), refer to Testing Library’s Vue API for user-centric testing.render(), screen.getByText(), screen.queryByTestId(), and userEvent for simulating user interactions.@testing-library/jest-dom for matchers like toBeInTheDocument(), toHaveClass(), etc.describe(), it(), expect(), beforeEach(), afterEach(), etc. The API is similar to Jest’s, so it's easy to adapt if you're familiar with Jest.@testing-library/jest-dom if you’re using Testing Library.@types/testing-library__jest-dom for jest-dom matchers).tsconfig.json includes types for any test libraries you’re using to get autocompletion and type checking in your .test.ts files.docs: chai assertion
assert
expect and should
docs: vitest
docs: @vue/test-utils
docs: @nuxt/test-utils
$fetch(url)fetch(url)url(path)- `mountSuspended`: wraps mount from [@vue/test-utils](#vue-test-utils), so you can check out the Vue Test Utils documentation for more on the options you can pass, and how to use this utility.
In a structured setup, there’s an implicit “dependency graph” based on which tool depends on the other:
Start by examining the highest-level library in your stack, which in this case is @nuxt/test-utils. Often, the documentation for these higher-level utilities will state what it includes or modifies by default. In the case of @nuxt/test-utils, it may already:
@vue/test-utils methods like mount and render.Tip: Look for documentation or guides on default configurations or opinionated setups in higher-level libraries. For example, @nuxt/test-utils might already configure things like component mounting in a way that works seamlessly with Nuxt’s SSR and file structure.
Instead of fully configuring each layer from the ground up:
@nuxt/test-utils and Vitest, and attempt to run a simple test.For example:
toBeInTheDocument throws an error, you’ll know you need to add @testing-library/jest-dom.@vue/test-utils or additional configurations.This iterative process lets you use the defaults from each tool, only adding configurations when necessary.
Frameworks like Nuxt often provide guides for “recommended” setups, which handle the nuances of their specific environment. In the case of @nuxt/test-utils:
When adding libraries at different levels, you might encounter overlapping configurations. For example:
@vue/test-utils and @nuxt/test-utils provide mount, but the @nuxt/test-utils version includes Nuxt-specific setups. Avoid importing mount from @vue/test-utils directly if you’re using @nuxt/test-utils, as it could lead to redundancy.@testing-library/vue wrap around @vue/test-utils but with added query functions. If you’re using @testing-library/vue, you may not need to rely on all of @vue/test-utils directly.Rule of Thumb: Always prefer the higher-level library’s functions first, as they’re usually tailored for your stack. Only bring in lower-level utilities if you explicitly need something more granular.
Once your setup works, you can safely experiment by removing or commenting out configurations to see if they are truly necessary.
@vue/test-utils methods directly but find that @nuxt/test-utils handles everything, you may not need that direct import.This approach helps you keep configurations minimal while verifying that each setting is required.
Start with Nuxt and Vitest Configurations Only:
@nuxt/test-utils and Vitest.vitest.config.ts), just specifying globals if needed.Add Simple Tests and Identify Gaps:
@nuxt/test-utils. If component rendering works, you know it’s handling @vue/test-utils setups.toBeInTheDocument), then add @testing-library/jest-dom in a setup file.Iterate As Needed:
@testing-library/vue if you need advanced DOM querying that isn’t easily handled by @nuxt/test-utils alone.By starting at the highest level (@nuxt/test-utils), adding only what’s missing, and experimenting with minimal configurations, you can avoid redundant setups. This pragmatic approach gives you a working environment with the least configuration, leveraging each library’s defaults and higher-level abstractions.
In short:
When a higher-level library or utility says it “wraps” certain methods from a lower-level library, it generally means it provides its own versions of those methods, with added configurations, behavior, or defaults specific to the higher-level library's context. In practice, this has a few important implications for setup, configuration, and usage:
When a method is “wrapped” by a higher-level library, it often comes pre-configured with defaults tailored to the specific environment or framework (like Nuxt, in your case). This means:
Example: @nuxt/test-utils might wrap mount from @vue/test-utils to automatically include SSR capabilities and load Nuxt-specific plugins and components. If you were using @vue/test-utils directly, you’d likely have to configure these things manually.
Pragmatically, this means you should:
Example: If @nuxt/test-utils provides a mount method, you should use that mount instead of importing mount from @vue/test-utils. The Nuxt wrapper likely includes necessary configurations (like handling the Nuxt context), which @vue/test-utils on its own wouldn’t.
Wrapped methods often come with additional functionality or convenience features that the lower-level method doesn’t provide. These could include:
Example: When @nuxt/test-utils wraps mount, it might add support for Nuxt’s specific features like pages, layouts, and async data fetching, allowing you to test Nuxt components without needing to mock those features manually.
Sometimes, wrapped methods may abstract away certain options, making it harder to customize specific low-level behavior. In rare cases where you need fine-grained control:
Example: If you’re testing something highly specific to Vue without Nuxt-specific behavior, you might prefer using @vue/test-utils’s mount to avoid any extra configuration or behavior that @nuxt/test-utils injects.
The higher-level library’s documentation usually specifies what its wrapped methods include, so:
When you see “we wrap XYZ methods like A and B,” here’s a practical approach:
By relying on wrapped methods, you save time on setup and avoid unnecessary configurations, as the higher-level library is handling many details for you.