Testing basics
An automated test is code that renders a component, performs user actions and checks the result. Tests run with one command and tell you right away if a change broke something. Vite projects usually use Vitest, and for Solid components there is the @solidjs/testing-library package.
Installation
npm install -D vitest jsdom @solidjs/testing-libraryvitest runs the tests, jsdom imitates a browser inside Node.js, and the testing library renders components and finds elements the way a user sees them.
Setup
Add a test section to vite.config.js:
// vite.config.js
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
export default defineConfig({
plugins: [solidPlugin()],
server: { port: 3000 },
test: {
environment: 'jsdom',
},
});In package.json, add the line "test": "vitest" to the scripts section.
The first test
Let us check the counter from the lesson on signals. A test sits next to the component in a file ending in .test.jsx:
// src/components/Counter.test.jsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@solidjs/testing-library';
import Counter from './Counter';
describe('Counter', () => {
it('increments the number on click', () => {
render(() => <Counter />);
fireEvent.click(screen.getByRole('button', { name: 'Mark done' }));
expect(screen.getByText('Tasks done: 1')).toBeTruthy();
});
});npm testThe terminal shows a line with a green check mark and the test name. Vitest keeps watching the files and reruns the test on every save.
What happens here
| Line | Meaning |
|---|---|
render(() => <Counter />) | Render the component; as in src/index.jsx, a function is passed |
screen.getByRole(...) | Find the button by its role and label |
fireEvent.click(...) | Click it |
screen.getByText(...) | Find an element with the text; if it is missing, the test fails |
Signals in Solid update the DOM immediately, so the check can come right after the click, without waiting.
Testing data
Functions without UI are even easier to test — no render needed. Suppose you moved the filter logic from the Switch lesson into src/utils/filterTodos.js as a function filterTodos(todos, filter):
// src/utils/filterTodos.test.js
import { it, expect } from 'vitest';
import { filterTodos } from './filterTodos';
it('keeps only active tasks', () => {
const todos = [{ id: 1, done: true }, { id: 2, done: false }];
expect(filterTodos(todos, 'active')).toEqual([{ id: 2, done: false }]);
});Such checks are fast and a good place to start.
Common mistake
Finding elements by classes or markup structure, such as container.querySelector('.btn'). Any layout change breaks such a test, although nothing changed for the user. Find elements by role and text — the way a person finds them.