Component testing definition and purpose
Component testing is a testing strategy that focuses on testing individual UI components in isolation. Unlike end-to-end tests that test entire user flows, component tests verify that each component works correctly on its own, making them faster to run and easier to debug.
Vitest framework support for component testing
Vitest provides comprehensive support for component testing across multiple frameworks including Vue, React, Svelte, Lit, Preact, Qwik, Solid, Marko, and more.
Advantages of component testing
Component testing offers several advantages: faster feedback by testing individual components without loading entire applications, isolated testing to focus on component behavior without external dependencies, better debugging to pinpoint issues in specific components, and comprehensive coverage to test edge cases and error states more easily.
Browser Mode for component testing
Component testing in Vitest uses Browser Mode to run tests in real browser environments using Playwright, WebdriverIO, or preview mode. This provides the most accurate testing environment as components run in real browsers with actual DOM implementations, CSS rendering, and browser APIs.
What Browser Mode catches that DOM simulation libraries miss
Browser Mode catches issues that DOM simulation libraries might miss, including CSS layout and styling problems, real browser API behavior, accurate event handling and propagation, and proper focus management and accessibility features.
Good component test characteristics
Good component tests focus on behavior and user experience rather than implementation details. They test the contract of how components receive inputs (props) and produce outputs (events, renders), test user interactions like clicks, form submissions, and keyboard navigation, test edge cases such as error states, loading states, and empty states, and avoid testing internals like state variables, private methods, and CSS classes.
Component testing hierarchy
The recommended testing hierarchy for components is: 1. Critical User Paths (always test these), 2. Error Handling (test failure scenarios), 3. Edge Cases (empty data, extreme values), 4. Accessibility (screen readers, keyboard nav), 5. Performance (large datasets, animations).
Isolation strategy for component testing
Test components in isolation by mocking dependencies. For API requests, MSW (Mock Service Worker) is recommended. For module mocking, use the import() syntax with vi.mock() to mock child components and focus on parent logic.
Integration strategy for component testing
Integration strategy involves testing component collaboration and data flow between multiple components, verifying how components work together and how data flows between them.
Official Vitest browser packages for frameworks
Vitest provides official packages for popular frameworks: vitest-browser-vue for Vue, vitest-browser-react for React, and vitest-browser-svelte for Svelte.
Testing Library integration with Vitest
You can integrate Testing Library with Vitest for frameworks not yet officially supported. The key is using page.elementLocator() to bridge Testing Library's DOM output with Vitest's browser mode APIs, allowing Testing Library to render components while Vitest provides interactions and assertions.
Available Testing Library packages for Vitest
Popular Testing Library packages that work with Vitest include: @testing-library/solid for Solid.js, @marko/testing-library for Marko, @testing-library/svelte as an alternative to vitest-browser-svelte, and @testing-library/vue as an alternative to vitest-browser-vue.
Best practice: Use Browser Mode for CI/CD
Ensure tests run in real browser environments for the most accurate testing. Browser Mode provides accurate CSS rendering, real browser APIs, and proper event handling.
Best practice: Test user interactions
Simulate real user behavior using Vitest's Interactivity API. Use page.getByRole() and userEvent methods rather than directly manipulating component state or internal implementation details.
Best practice: Test accessibility
Ensure components work for all users by testing keyboard navigation, focus management, and ARIA attributes. This includes testing keyboard navigation with Tab and Shift+Tab, and verifying ARIA attributes on interactive elements.
Best practice: Mock external dependencies
Focus tests on component logic by mocking APIs and external services. This makes tests faster and more reliable. For API requests, MSW (Mock Service Worker) is recommended as it provides more realistic request/response mocking.
Best practice: Use meaningful test descriptions
Write test descriptions that explain the expected behavior, not implementation details. Good examples: 'shows error message when email format is invalid' or 'disables submit button while form is submitting'. Avoid implementation-focused descriptions like 'calls validateEmail function' or 'sets isSubmitting state to true'.
Component isolation strategy example
Example of testing component isolation: vi.mock(import('../components/UserCard'), () => ({ default: vi.fn(({ user }) => `<div>User: ${user.name}</div>`) })) and then render(<UserProfile userId="123" />) to test the parent component in isolation with mocked child components.
Integration testing example with filters
Example testing component collaboration: render(<ProductList products={mockProducts} />) with mock data, then use userEvent.selectOptions() to interact with filters and await expect.element() to verify the filtered results are displayed correctly.
Testing Library integration pattern with Solid.js
For Solid.js components not yet with official support, use: import { render } from '@testing-library/solid'; const { baseElement, getByRole } = render(() => <Counter initialValue={0} />); const screen = page.elementLocator(baseElement); then use Vitest's page queries for finding elements and expect.element() for assertions.
MSW setup for async component testing
MSW setup example: import { http, HttpResponse } from 'msw'; import { setupWorker } from 'msw/browser'; const worker = setupWorker(http.get('/api/users/:id', ({ params }) => HttpResponse.json({ id: params.id, name: 'John Doe', email: 'john@example.com' }))); beforeAll(() => worker.start()); afterEach(() => worker.resetHandlers()); afterAll(() => worker.stop());
Testing stateful component state transitions
Example of testing stateful components: render(<ShoppingCart />), await expect.element(getByText('Your cart is empty')).toBeInTheDocument(), await page.getByRole('button', { name: /add laptop/i }).click(), await expect.element(getByText('1 item')).toBeInTheDocument(), then test quantity updates with additional clicks.
Testing component communication with mocked callback
Example testing parent-child communication: const mockOnSelectionChange = vi.fn(); render(<ProductCatalog onSelectionChange={mockOnSelectionChange}><ProductFilter /><ProductGrid /></ProductCatalog>); await page.getByRole('checkbox', { name: /electronics/i }).click(); expect(mockOnSelectionChange).toHaveBeenCalledWith({ category: 'electronics', filters: ['electronics'] });
Testing complex form validation
Example testing form with validation: get form inputs using page.getByLabelText(), test validation by clicking submit without filling fields and expecting error messages, fill fields partially and verify only relevant errors remain, test email format validation, then test successful submission with all fields valid.
Testing error boundaries
Example testing error boundary: render component with shouldThrow={false} and verify normal rendering, then rerender with shouldThrow={true} and verify the error boundary displays the fallback UI 'Something went wrong'.
Testing modal accessibility
Example testing modal accessibility: get modal element with getByRole('dialog'), verify it has focus with await expect.element(modal).toHaveFocus(), verify ARIA attributes with toHaveAttribute('aria-modal', 'true'), test Escape key closes modal with userEvent.keyboard('{Escape}'), and test focus trap with Shift+Tab wrapping to last element.
Debugging component tests with browser dev tools
Browser Mode runs tests in real browsers, giving access to full developer tools. When tests fail, you can open browser dev tools (F12 or right-click → Inspect), set breakpoints in test or component code, inspect the DOM to see actual rendered output, check console errors, and monitor network requests. For headful mode debugging, add headless: false to browser config temporarily.
Debugging with expect.element auto-retry
expect.element will automatically retry and show helpful error messages when assertions fail. This helps identify if elements are truly missing, not visible, or just not yet rendered due to async operations.
Debugging element selector issues
When selectors fail, check accessible names of buttons and other elements, try multiple query strategies using .or() for auto-retrying, verify elements are visible and enabled with toBeVisible(), and use console.log to check element counts and attributes.
Migration from Jest and Testing Library
Most Jest + Testing Library tests work with minimal changes in Vitest. Main changes: replace import from @testing-library/react with import from vitest-browser-react, use await expect.element() instead of expect() for DOM assertions, and use vitest/browser for user interactions instead of @testing-library/user-event.
toHaveTextContent now performs strict equality in Vitest 5.0
In Vitest 5.0, the browser-mode toHaveTextContent matcher now validates exact equality instead of partial, case-sensitive match. Regular expressions are no longer accepted. The previous behavior (partial and regex matching) has moved to the new toMatchTextContent matcher.
render is async in vitest-browser-vue and vitest-browser-svelte
In Vitest 5.0, vitest-browser-vue and vitest-browser-svelte packages now return a promise from render(). The call must be awaited before querying rendered output. Example: const screen = await render(Component).
vitest-browser-svelte render function basic usage
The render function from vitest-browser-svelte is used to render Svelte components in Browser Mode. It takes a component and optional options including props and render options, and returns a Promise that resolves to a RenderResult. The function signature is: render<C extends Component>(Component: ComponentImport<C>, options?: ComponentOptions<C>, renderOptions?: SetupOptions): Promise<RenderResult<C>>. It records a svelte.render trace mark visible in the Trace View.
vitest-browser-svelte render options: props and target
The render function accepts props directly as options, or can specify props explicitly. By default, Vitest creates a div, appends it to document.body, and renders the component there. You can provide a custom target HTMLElement container, which will not be appended automatically—you must call document.body.appendChild(container) before render. This is useful when unit testing elements like tbody that cannot be children of a div.
vitest-browser-svelte render options: baseElement
The baseElement option is passed in the third argument to render. If target is specified, baseElement defaults to that; otherwise it defaults to document.body. It is used as the base element for queries and for what is printed when using debug().
vitest-browser-svelte RenderResult includes locators
The render function returns a RenderResult that includes all available locators relative to the baseElement, including custom ones. These locators support methods like getByRole, getByText, and custom locators defined via locators.extend API.
vitest-browser-svelte RenderResult container property
The RenderResult includes a container property which is the containing DOM node where the Svelte component is rendered. It is a regular DOM node that supports methods like querySelector, but locators should be preferred over using container for querying elements because locators are more resilient to component changes.
vitest-browser-svelte RenderResult component property
The RenderResult includes a component property which is the mounted Svelte component instance. This allows access to component methods and properties if needed.
vitest-browser-svelte debug method
The debug method is a shortcut for console.log(prettyDOM(baseElement)). It prints the DOM content of the container or specified elements to the console. Signature: function debug(el?: HTMLElement | HTMLElement[] | Locator | Locator[]): void
vitest-browser-svelte rerender method
The rerender method updates the component's props and waits for Svelte to apply the changes. It is used to test how a component responds to prop changes. Signature: function rerender(props: Partial<ComponentProps<T>>): Promise<void>. It records a svelte.rerender trace mark in the Trace View.
vitest-browser-svelte unmount method
The unmount method unmounts and destroys the Svelte component. Signature: function unmount(): Promise<void>. It records a svelte.unmount trace mark in the Trace View. This is useful for testing what happens when a component is removed from the page, such as testing that event handlers are not left hanging around causing memory leaks.
vitest-browser-svelte custom locators via locators.extend
To extend locator queries and make render return custom locators, define them using the locators.extend API from vitest/browser. Custom locators are defined as methods that return selector strings.
vitest-browser-svelte entry points: pure and default
The vitest-browser-svelte package exposes two entry points: vitest-browser-svelte and vitest-browser-svelte/pure. They expose identical API, but the pure entry point doesn't add a handler to remove the component before the next test has started.
vitest-browser-svelte benefits over @testing-library/svelte
vitest-browser-svelte returns APIs that interact well with built-in locators, user events, and assertions. Vitest automatically retries the element until the assertion is successful, even if it was rerendered between assertions, which is a benefit that @testing-library/svelte lacks.
vitest-browser-svelte testing snippets with wrapper components
For simple Svelte snippets, you can use a wrapper component and dummy children to test them. Setting data-testid attributes can be helpful when testing slots in this manner.
vitest-browser-svelte testing complex snippets with createRawSnippet
For more complex snippets where you want to check arguments, use Svelte's createRawSnippet API. This allows passing snippets as props to components and validating what content they render.
vitest-browser-svelte basic render example
Example: import { render } from 'vitest-browser-svelte'; import { expect, test } from 'vitest'; import Component from './Component.svelte'; test('counter button increments the count', async () => { const screen = await render(Component, { initialCount: 1, }); await screen.getByRole('button', { name: 'Increment' }).click(); await expect.element(screen.getByText('Count is 2')).toBeVisible() })
vitest-browser-vue render function returns Promise with RenderResult
The render function is imported from vitest-browser-vue and accepts a Vue component and optional ComponentRenderOptions. It returns a Promise that resolves to a RenderResult containing all available locators relative to the baseElement, including custom ones.
render container option for custom DOM elements
The container option allows you to provide your own HTMLElement as the render target. By default, Vitest creates a div and appends it to document.body. If you provide a custom container, it will not be appended automatically — you must call document.body.appendChild(container) before rendering. This is useful for unit testing elements like tbody that cannot be children of a div.
render baseElement option defaults and usage
If container is specified, baseElement defaults to that container, otherwise it defaults to document.body. baseElement is used as the base element for queries and what is printed when debug() is called.
RenderResult container property for DOM inspection
The render result includes a container property that is the regular DOM node where the Vue component is rendered. You can technically call container.querySelector on it, but using locators is recommended for better resilience to component changes.
RenderResult locator property for scoped queries
The render result includes a locator property that represents a locator of your container. It is useful for queries scoped only to your component or passing down to other assertions.
debug method signature and behavior
The debug method signature is: function debug(el?: HTMLElement | HTMLElement[] | Locator | Locator[], maxLength?: number, options?: PrettyDOMOptions): void. It is a shortcut for console.log(prettyDOM(baseElement)) and will print the DOM content of the container or specified elements to the console.
rerender method to update component props
The rerender method accepts Partial<Props> and returns Promise<void>. It allows you to update the props of an already-rendered component in your test. It also records a vue.rerender trace mark in the Trace View.
unmount method to remove rendered component
The unmount method returns Promise<void> and causes the rendered component to be unmounted. It records a vue.unmount trace mark in the Trace View. This is useful for testing what happens when your component is removed, such as ensuring you don't leave event handlers hanging around causing memory leaks.
emitted method signature for accessing component events
The emitted method has two overloads: emitted<T = unknown>(): Record<string, T[]> returns all emitted events from the component, and emitted<T = unknown[]>(eventName: string): undefined | T[] returns the emitted events for a specific event name.
cleanup function removes all rendered components
The cleanup function exported from vitest-browser-vue removes all components rendered with the render function.
vitest-browser-vue supports all @vue/test-utils mount options
The render function supports all mount options from @vue/test-utils except attachTo (use container instead). In addition to those, there are also container and baseElement options.
render function records vue.render trace mark
The render function records a vue.render trace mark that is visible in the Trace View, allowing you to trace the rendering operation.
Custom locators with locators.extend API
You can extend locator queries using the locators.extend API from vitest/browser. Define custom locator functions that will be added to the result of render. For example: locators.extend({ getByArticleTitle(title) { return `[data-title="${title}"]` } })