- 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
.rejects
Mounting:
mount()
shallowMount()
Mount Options:
attachTo
attrs
data
props
slots
scopedSlots
global
global.components
global.directives
global.mocks
global.provide
global.plugins
global.stubs
shallow
Wrapper 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.global
config.plugins
RouterLinkStub
TESTING LIBRARY VUE SYNTAX
Rendering:
render()
screen
cleanup()
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.