{ "id": "d1bd7b019b4940d72c51b6f9", "source": "solid:signals", "type": "github-document", "title": "testing", "content": "---\ntitle: Testing\norder: 6\nuse_cases: \u003e-\n testing components, unit tests, integration tests, user interactions, test\n coverage, quality assurance\ntags:\n - testing\n - vitest\n - components\n - unit-tests\n - quality\nversion: '1.0'\ndescription: \u003e-\n Test Solid apps with Vitest and Testing Library. Write component tests,\n simulate user interactions, and ensure code quality effectively.\n---\n\nTesting your Solid applications is important to inspiring confidence in your codebase through preventing regressions.\n\n## Getting started\n\n### Testing packages explanations\n\n- [`vitest`](https://vitest.dev) - testing framework that includes runner, assertion engine, and mocking facilities\n- [`jsdom`](https://github.com/jsdom/jsdom) - a virtual DOM used to simulate a headless browser environment running in node\n- [`@solidjs/testing-library`](https://github.com/solidjs/solid-testing-library/blob/main/README.md) - a library to simplify testing components, directives, and primitives, with automatic cleanup\n- [`@testing-library/user-event`](https://testing-library.com/docs/user-event/intro) - used to simulate user events that are closer to reality\n- [`@testing-library/jest-dom`](https://testing-library.com/docs/ecosystem-jest-dom) - augments expect with helpful matchers\n\n### Adding testing packages\n\nThe recommended testing framework for Solid applications is [vitest](https://vitest.dev).\n\nTo get started with vitest, install the following development dependencies:\n\n```package-install-dev\nvitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom\n```\n\n### Testing configuration\n\nIn your `package.json` add a `test` script calling `vitest`:\n\n```json title=\"package.json\"\n \"scripts\": {\n \"test\": \"vitest\"\n }\n```\n\nIt is not necessary to add `@testing-library/jest-dom` to the testing options in `vite.config`, since `vite-plugin-solid` automatically detects and loads it if present.\n\n#### TypeScript configuration\n\nIf using TypeScript, add `@testing-library/jest-dom` to `tsconfig.json#compilerOptions.types`:\n\n```json title=\"tsconfig.json\"\n \"compilerOptions\": {\n // ...\n \"jsx\": \"preserve\",\n \"jsxImportSource\": \"solid-js\",\n \"types\": [\"vite/client\", \"@testing-library/jest-dom\"]\n }\n```\n\n#### SolidStart configuration\n\nWhen using [SolidStart](/solid-start), create a `vitest.config.ts` file:\n\n```ts title=\"vitest.config.ts\"\nimport solid from \"vite-plugin-solid\"\nimport { defineConfig } from \"vitest/config\"\n\nexport default defineConfig({\n plugins: [solid()],\n resolve: {\n conditions: [\"development\", \"browser\"],\n },\n})\n```\n\n## Writing tests\n\n### Components testing\n\nTesting components involves three main things:\n\n- Rendering the component\n- Interacting with the component\n- Validating assertions\n\nTo write tests for your components, create a `[name].test.tsx` file.\nThe purpose of this file is to describe the intended behavior from a user's perspective in the form of unit tests:\n\n```jsx tab title=\"Counter.test.jsx\"\nimport { test, expect } from \"vitest\"\nimport { render } from \"@solidjs/testing-library\"\nimport userEvent from \"@testing-library/user-event\"\nimport { Counter } from \"./Counter\"\n\nconst user = userEvent.setup()\n\ntest(\"increments value\", async () =\u003e {\n const { getByRole } = render(() =\u003e \u003cCounter /\u003e)\n const counter = getByRole('button')\n expect(counter).toHaveTextContent(\"1\")\n await user.click(counter)\n expect(counter).toHaveTextContent(\"2\")\n})\n```\n\n```jsx tab title=\"Counter.jsx\"\nexport const Counter = () =\u003e {\n const [count, setCount] = createSignal(1);\n return (\n \u003cbutton onClick={() =\u003e setCount(count() + 1)}\u003e\n {count()}\n \u003c/button\u003e\n );\n}\n```\n\nIn the `test.jsx` file, [the `render` call from `@solidjs/testing-library`](https://testing-library.com/docs/solid-testing-library/api#render) is used to render the component and supply the props and context.\nTo mimic a user interaction, `@testing-library/user-event` is used.\nThe [`expect` function provided by `vitest`](https://vitest.dev/api/expect.html) is extended with a [`.ToHaveTextContent(\"content\")` matcher from `@testing-library/jest-dom`](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tohavetextcontent) to supply what the expected behavior is for this component.\n\nTo run this test, use the following command:\n\n```package-run\ntest\n```\n\nIf running the command is successful, you will get the following result showing whether the tests have passed or failed:\n\n```ansi frame=\"none\"\n\u001b[1;36m[RUN]\u001b[0;36m v1.4.0\u001b[0;8m solid-app/src/components/Counter.test.tsx\u001b[0m\n\n\u001b[0;32m ✓ \u001b[0;8msrc/components/\u001b[1;1mCounter\u001b[0;8m.test.tsx (1)\n\u001b[0;32m ✓ \u001b[0;8m\u001b[1;1m\u003cCounter /\u003e\u001b[0;8m (1)\n\u001b[0;32m ✓ \u001b[0;8m\u001b[1;1mincrements value\u001b[0;8m\n\n Test Files \u001b[1;32m1 passed\u001b[0;8m (1)\n Tests \u001b[1;32m1 passed\u001b[0;8m (1)\n Start at \u001b[1;1m16:51:19\u001b[0;8m\n Duration \u001b[1;1m4.34s\u001b[0;8m (transform 1.01s, setup 205ms, collect 1.54s, tests 155ms,\nenvironment 880ms, prepare 212ms)\n\n```\n\n#### Rendering the component\n\nThe `render` function from `@solidjs/testing-library` creates the testing environment within the `test.tsx` file.\nIt sets up the container, rendering the component within it, and automatically registers it for clean-up after a successful test.\nAdditionally, it manages wrapping the component in contexts as well as setting up a router.\n\n```tsx frame=\"none\"\nconst renderResult = render(\n () =\u003e \u003cMyComponent /\u003e, // @solidjs/testing-library requires a function\n { // all options are optional\n container, // manually set up your own container, will not be handled\n baseElement, // parent of container in case it is not supplied\n queries, // manually set up custom queries\n hydrate, // set to `true` to use hydration\n wrapper, // reusable wrapper component to supply context\n location, // sets up a router pointed to the location if provided\n }\n)\nconst {\n asFragment, // function returning the contents of the container\n baseElement, // the parent of the container\n container, // the container in which the component is rendered\n debug, // a function giving some helpful debugging output\n unmount, // manually removing the component from the container\n ...queries, // functions to select elements from the container\n} = renderResult\n```\n\n##### Using the right queries\n\nQueries are helpers used to find elements within a page.\n\n```\n ⎧ Role\n get ⎫ By ⎪ DisplayValue\n query ⎬ ⎨ LabelText\n find ⎭ AllBy ⎪ Text\n ⎩ ...\n```\n\nThe prefixes (`get`, `query`, and `find`) and the middle portion (`By` and `AllBy`) depend on if the query should wait for an element to appear (or not), whether it should throw an error if the element cannot be found, and how it should handle multiple matches:\n\n- **getBy**: synchronous, throws if not found or more than 1 matches\n- **getAllBy**: synchronous, throws if not found, returns array of matches\n- **queryBy**: synchronous, null if not found, error if more than 1 matches\n- **queryAllBy**: synchronous, returns array of zero or more matches\n- **findBy**: asynchronous, rejected if not found within 1000ms or more than 1 matches, resolves with element if found\n- **findAllBy**: asynchronous, rejected if not found within 1000ms, resolves with array of one or more element(s)\n\nBy default, queries should start with `get...`.\nIf there are multiple elements matching the same query, `getAllBy...` should be used, otherwise use `getBy...`.\n\nThere are two exceptions when you should **not** start with `get...`:\n\n1. If the `location` option is used or the component is based on resources, the router will be lazy-loaded; in this case, the first query after rendering needs to be `find...`\n2. When testing something that is *not* rendered, you will need to find something that will be rendered at the same time; after that, use `queryAllBy...` to test if the result is an empty array (`[]`).\n\nThe query's suffix (Role, LabelText, ...) depends on the characteristics of the element you want to select.\nIf possible, try to select for accessible attributes (roughly in the following order):\n\n- **Role**: [WAI ARIA](https://www.w3.org/WAI/standards-guidelines/aria) landmark roles which are automatically set by semantic elements like `\u003cbutton\u003e` or otherwise use `role` attribute\n- **LabelText**: elements that are described by a label wrapping the element, or by an `aria-label` attribute, or is linked with `for`- or `aria-labelledby` attribute\n- **PlaceholderText**: input elements with a `placeholder` attribute\n- **Text**: searches text within all text nodes in the element, even if split over multiple nodes\n- **DisplayValue**: form elements showing the given value (e.g. select elements)\n- **AltText**: images with alt text\n- **Title**: HTML elements with the `title` attribute or SVGs with the `\u003ctitle\u003e` tag containing the given text\n- **TestId**: queries by the `data-testid` attribute; a different data attribute can be setup via `configure({testIdAttribute: 'data-my-test-attribute'})`; TestId-queries are *not accessible*, so use them only as a last resort.\n\nFor more information, check the [testing-library documentation](https://testing-library.com/docs/queries/about).\n\n#### Testing through Portal\n\nSolid allows components to break through the DOM tree structure using [`\u003cPortal\u003e`](/reference/components/portal). This mechanism will still work in testing, so the content of the portals will break out of the testing container. In order to test this content, make sure to use the `screen` export to query the contents:\n\n```jsx tab title=\"Toast.test.jsx\"\nimport { test, expect } from \"vitest\"\nimport { render, screen } from \"@solidjs/testing-library\"\nimport { Toast } from \"./Toast\"\n\ntest(\"increments value\", async () =\u003e {\n render(() =\u003e \u003cToast\u003e\u003cp\u003eThis is a toast\u003c/p\u003e\u003c/Toast\u003e)\n const toast = screen.getByRole(\"log\")\n expect(toast).toHaveTextContent(\"This is a toast\")\n})\n```\n\n```jsx tab title=\"Toast.jsx\"\nimport { Portal } from \"solid-js/web\";\n\nexport const Toast = (props) =\u003e {\n return (\n \u003cPortal\u003e\n \u003cdiv class=\"toast\" role={props.role ?? \"log\"}\u003e\n {props.children}\n \u003c/div\u003e\n \u003c/Portal\u003e\n );\n}\n```\n\n#### Testing in context\n\nIf a component relies on some context, to wrap it use the `wrapper` option:\n\n```tsx title=\"Context.test.tsx\"\nimport { test, expect } from \"vitest\"\nimport { render } from \"@solidjs/testing-library\"\nimport { DataContext, DataConsumer } from \"./Data\"\n\nconst wrapper = (props) =\u003e \u003cDataContext value=\"test\" {...props} /\u003e\n\ntest(\"receives data from context\", () =\u003e {\n const { getByText } = render(() =\u003e \u003cDataConsumer /\u003e, { wrapper })\n expect(getByText(\"test\")).toBeInTheDocument()\n});\n```\n\nWrappers can be re-used if they are created externally.\nFor wrappers with different values, a higher-order component creating the required wrappers can make the tests more concise:\n\n```tsx\nconst createWrapper = (value) =\u003e (props) =\u003e\n \u003cDataContext value={value} {...props}/\u003e\n```\n\n:::note[Using multiple providers]\nIf using multiple providers, [solid-primitives has `\u003cMultiProvider\u003e`](https://primitives.solidjs.community/package/context#multiprovider) to avoid nesting multiple levels of providers\n:::\n\n##### Testing routes\n\nFor convenience, the `render` function supports the `location` option that wraps the rendered component in a router pointing at the given location.\nSince the `\u003cRouter\u003e` component is lazily loaded, the first query after rendering needs to be asynchronous, i.e. `findBy...`:\n\n```tsx\nconst { findByText } = render(\n () =\u003e \u003cRoute path=\"/article/:id\" component={Article} /\u003e,\n { location: \"/article/12345\" }\n);\nexpect(await findByText(\"Article 12345\")).toBeInTheDocument()\n```\n\n#### Interacting with components\n\nMany components are not static, rather they change based on user interactions.\nTo test these changes, these interactions need to be simulated.\nTo simulate user interactions, `@testing-library/user-event` library can be used.\nIt takes care of the usual order of events as they would occur in actual user interactions.\nFor example, this means that a `click` event from the user would be accompanied by `mousemove`, `hover`, `keydown`, `focus`, `keyup`, and `keypress`.\n\nThe most convenient events to test are typically `click`, `keyboard` and `pointer` (to simulate touch events).\nTo dive deeper into these events, you can learn about them in the [`user-event` documentation](https://testing-library.com/docs/user-event/intro).\n\n##### Using timers\n\nIf you require a fake timer and want to use `vi.useFakeTimers()` in your tests, it must set it up with an `advanceTimers` option:\n\n```tsx title=\"user-event.test.tsx\"\nimport { vi } from \"vitest\"\n\nconst user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime })\n\nvi.useFakeTimers()\n\ndescribe(\"pre-login: sign-in\", () =\u003e {\n const { getByRole, getByLabelText } = render(() =\u003e \u003cUser /\u003e)\n const signUp = getByRole('button', { text: 'Sign-in' })\n // use convenience API click:\n user.click(signUp)\n const name = getByLabelText('Name')\n // use complex keyboard input:\n user.keyboard(name, \"{Shift}test{Space}{Shift}user\")\n const password = getByLabelText('Password')\n user.keyboard(name, \"secret\")\n const login = getByRole('button', { text: 'Login' })\n // use touch event\n user.pointer([\n { keys: \"[TouchA]\" target: login },\n { keys: \"[/TouchA]\", target: login }\n ])\n});\n```\n\n#### Validating assertions\n\n`vitest` comes with the `expect` function to facilitate assertions that works like:\n\n```tsx frame=\"none\"\nexpect(subject)[assertion](value)\n```\n\nThe command supports assertions like `toBe` (reference comparison) and `toEqual` (value comparison) out of the box.\nFor testing inside the DOM, the package `@testing-library/jest-dom` augments it with some helpful additional assertions:\n\n- [`.toBeInTheDocument()`](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tobeinthedocument) - checks if the element actually exists in the DOM\n- [`.toBeVisible()`](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tobevisible) - checks if there is no reason the element should be hidden\n- [`.toHaveTextContent(content)`](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tohavetextcontent) - checks if the text content matches\n- [`.toHaveFocus()`](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tohavefocus) - checks if this is the currently focused element\n- [`.toHaveAccessibleDescription(description)`](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tohaveaccessibledescription) - checks accessible description\n- and a [lot more](https://github.com/testing-library/jest-dom?tab=readme-ov-file#custom-matchers).\n\n### Directive testing\n\n[Directives](/reference/jsx-attributes/use) are reusable behaviors for elements.\nThey receive the HTML element they are bound to as their first and an accessor of the directive prop as their second argument.\nTo make testing them more concise, [`@solidjs/testing-library` has a `renderDirective`](https://testing-library.com/docs/solid-testing-library/api#renderdirective) function:\n\n```ts frame=\"none\"\nconst renderResult = renderDirective(directive, {\n initialValue, // value initially added to the argument signal\n targetElement, // opt. node name or element used as target for the directive\n ...renderOptions, // see render options\n})\nconst {\n arg, // getter for the directive's argument\n setArg, // setter for the directive's argument\n ...renderResults, // see render results\n} = renderResult\n```\n\nIn `...renderResults`, the container will contain the `targetElement`, which defaults to a `\u003cdiv\u003e`.\nThis, along with the ability to modify the `arg` signal, are helpful when testing directives.\n\nIf, for example, you have a directive that handles the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API), you can test it like this:\n\n```ts tab title=\"fullscreen.test.ts\"\nimport { test, expect, vi } from \"vitest\"\nimport { renderDirective } from \"@solidjs/testing-library\"\nimport { createFullScreen } from \"./fullscreen\"\n\ntest(\"toggles fullscreen\", () =\u003e {\n const targetElement = document.createElement(\"div\")\n const fs = vi.spyOn(targetElement, \"fullscreen\")\n const [setArg, container] = renderDirective(createFullScreen, false)\n setArg(true)\n expect(fs).toHaveBeenCalled()\n})\n```\n\n```ts tab title=\"fullscreen.ts\"\nimport { Accessor } from \"solid-js\"\n\nexport const fullscreen = (ref: HTMLElement, active: Accessor\u003cboolean\u003e) =\u003e\n createEffect(() =\u003e {\n const isActive = document.fullscreenElement === ref\n if (active() \u0026\u0026 !isActive) {\n ref.requestFullScreen().catch(() =\u003e {})\n } else if (!active() \u0026\u0026 isActive) {\n document.exitFullScreen()\n }\n })\n```\n\n### Primitive testing\n\nWhen the reference to an element is not needed, parts of state and logic can be put into reusable hooks or primitives.\nSince these do not require elements, there is no need for `render` to test them since it would require a component that has no other use.\nTo avoid this, there is a [`renderHook` utility](https://testing-library.com/docs/solid-testing-library/api#renderhook) that simulates a component without actually rendering anything.\n\n```ts frame=\"none\"\nconst renderResult = renderHook(hook, {\n initialProps, // an array with arguments being supplied to the hook\n wrapper, // same as the wrapper optionss for `render`\n})\nconst {\n result, // return value of the hook (mutable, destructuring fixes it)\n cleanup, // manually remove the traces of the test from the DOM\n owner, // the owner running the hook to use with `runWithOwner()`\n} = renderResult\n```\n\nA primitive that manages the state of a counter could be tested like this:\n\n```ts frame=\"none\"\nimport { test, expect } from \"vitest\"\nimport { renderHook } from \"@solidjs/testing-library\"\nimport { createCounter } from \"./counter\"\n\ntest(\"increments count\", () =\u003e {\n const { result } = renderHook(createCounter)\n expect(result.count).toBe(0)\n result.increment()\n expect(result.count).toBe(1)\n})\n```\n\n### Testing effects\n\nSince effects may happen asynchronously, it can be difficult to test them.\n[`@solidjs/testing-library` comes with a `testEffect` function](https://testing-library.com/docs/solid-testing-library/api#async-methods) that takes another function that receives a `done` function to be called once tests are over and returns a promise.\nOnce `done` is called, the returned promise is resolved.\nAny errors that would hit the next boundary are used to reject the returned promise.\n\nAn example test using `testEffect` may look like this:\n\n```ts frame=\"none\"\nconst [value, setValue] = createSignal(0)\nreturn testEffect(done =\u003e\n createEffect((run: number = 0) =\u003e {\n if (run === 0) {\n expect(value()).toBe(0)\n setValue(1)\n } else if (run === 1) {\n expect(value()).toBe(1)\n done()\n }\n return run + 1\n })\n)\n```\n\n### Benchmarks\n\nWhile Solid offers performance simplified, it is good to validate if that promise can be kept.\nVitest offers an experimental `bench` function to run benchmarks and compare the results inside the same `describe` block;\nfor example if you had a `\u003cList\u003e` flow component similar to `\u003cFor\u003e`, you could benchmark it like this:\n\n```jsx title=\"list.bench.jsx\"\ndescribe('list rendering', () =\u003e {\n const ITEMS = 1000\n const renderedFor = new Set()\n const listFor = Array.from({ length: ITEMS }, (_, i) =\u003e i)\n bench('For', () =\u003e new Promise((resolve) =\u003e {\n const ItemFor = (props) =\u003e {\n onMount(() =\u003e {\n renderedFor.add(props.number)\n if (renderedFor.size === ITEMS) { resolve() }\n })\n return \u003cspan\u003e{props.number}\u003c/span\u003e\n }\n render(() =\u003e \u003cFor each={listFor}\u003e\n {(item) =\u003e \u003cItemFor number={item} /\u003e}\n \u003c/For\u003e)\n }))\n\n const renderedList = new Set()\n const listList = Array.from({ length: ITEMS }, (_, i) =\u003e i)\n bench('List', () =\u003e new Promise((resolve) =\u003e {\n const ItemList = (props) =\u003e {\n onMount(() =\u003e {\n renderedList.add(props.number)\n if (renderedList.size === ITEMS) { resolve() }\n })\n return \u003cspan\u003e{props.number}\u003c/span\u003e\n }\n render(() =\u003e \u003cList each={listList}\u003e\n {(item) =\u003e \u003cItemList number={item} /\u003e}\n \u003c/List\u003e)\n }))\n})\n```\n\nRunning `[npm|pnpm|yarn] test bench` will then execute the benchmark function:\n\n```ansi frame=\"none\"\n\u001b[1;36m[RUN]\u001b[0;36m v1.4.0\u001b[0;8m solid-app/src/components/\u001b[0m\n\n\u001b[0;32m ✓ \u001b[0;8msrc/components/list.bench.jsx \u001b[0;31m(2)\u001b[0;8m 1364ms\n\u001b[0;32m ✓ \u001b[0;8mbenchmark\u001b[0;31m (2)\u001b[0;8m 1360ms\n\u001b[1;37m name hz min max mean p75 p99 p995 p999 rme samples\n\u001b[1;32m · \u001b[0;37mFor \u001b[0;36m60.5492 11.2355 47.9164 16.5155 15.4180 47.9164 47.9164 47.9164 \u001b[0;37m±13.60% 31 \u001b[0;32mfastest\n\u001b[1;32m · \u001b[0;37mList \u001b[0;36m49.7725 16.5441 69.3559 20.0914 18.0349 69.3559 69.3559 69.3559 \u001b[0;37m±21.37% 25\n\n\u001b[1;36m[BENCH]\u001b[0;36m Summary\n\n\u001b[0;37mFor - src/components/list.bench.tsx \u003e benchmark\n\u001b[0;32m 1.22x\u001b[0;8m faster than\u001b[0;37m List\n```\n\nPlease keep in mind that it is very difficult to create meaningful benchmarks.\nThe numbers should always be taken with a grain of salt, but can still indicate performance degradations if compared between versions.\n\n### Test coverage\n\nWhile coverage numbers can be misleading, they are used by many projects as a rough measurement of code quality.\nVitest supports coverage collection. To use it, it needs an extra package:\n\n```package-install-dev\n@vitest/coverage-v8\n```\n\nAlso, you need to [set up vitest's coverage feature](https://vitest.dev/guide/coverage.html).\n\n### Integration/E2E testing\n\nSome issues can only be found once the code is running in the environment it is supposed to run in.\nSince integration and end-to-end tests are agnostic to frameworks, all proven approaches will work equally for Solid.", "url": "https://github.com/solidjs/solid-docs/blob/HEAD/src/routes/guides/testing.mdx", "metadata": { "path": "src/routes/guides/testing.mdx", "repo": "solidjs/solid-docs", "repo_url": "https://github.com/solidjs/solid-docs.git", "size": 21862, "source_type": "github" }, "hash": "299cfb57f926fecb04951954b738bebc1369fab4ef9d649d2946a96890cfa520", "timestamp": "2026-02-23T11:43:00.188279229+01:00" }