Configuring support for Svelte component tests
A Svelte component test is one that, perhaps unsurprisingly, tests a Svelte component. For this, we need access to a Document Object Model (DOM), which isn’t part of the standard Node.js environment. We’ll also need some additional packages for writing unit test expectations against the DOM.
Installing jsdom and testing library helpers
At the Terminal, run the following command to install the jsdom package and @testing-library packages that we’ll use in our unit tests:
npm install --save-dev \ jsdom \ @testing-library/svelte \ @testing-library/jest-dom \ @testing-library/user-event
If you’re using TypeScript, at this point, you may wish to add packages containing type definitions.
Next, create a new file named src/vitest/registerMatchers.js with the following content. It ensures that the matchers we’ll be using are available for use via the expect function:
import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';
expect.extend(matchers);
Then, update vite.config.js to add a new environment property, which installs jsdom correctly, and also a setupFiles property, which ensures the file defined previously is loaded (and invoked) just before the test suites are loaded:
const config = {
plugins: [sveltekit()],
test: {
...,
reporter: 'verbose',
environment: 'jsdom',
setupFiles: ['./src/vitest/registerMatchers.js']
}
};
That’s it for the basic setup. Now let’s test it out.
Writing a test for the DOM
Open the src/index.test.js file and add the following test definition, inside the describe block. This test makes use of the document object that is created for us by the jsdom package, and the toHaveTextContent matcher that is provided by the @testing-library/jest-dom package:
it('renders hello into the document', () => {
document.body.innerHTML =
'<h1>Hello, world!</h1>';
expect(document.body).toHaveTextContent(
'Hello, world!'
);
});
Now, if you run the test, you should see it pass. But, just as you did with the first test, it’s important to confirm the test actually tests what it says it does. Change the test by commenting out or deleting the first line of the test, and then re-running the test runner.
You should see an output as follows:
FAIL src/index.test.js > sum test > renders hello into the document
Error: expect(element).toHaveTextContent()
Expected element to have text content:
Hello, world!
Received:
❯ src/index.test.js:9:25
7|
8| it('renders hello into the document', () => {
9| expect(document.body).toHaveTextContent(
| ^
10| 'Hello, world!'
11| );
That proves the test is working. You can go ahead and undo the breaking change you made.
Writing a first Svelte component test
Next, let’s write an actual Svelte component and test that out. Create a new file named src/Hello.svelte with the following content:
<script>
export let name;
</script>
<p>Hello, {name}!</p>
Then, go back to the src/index.test.js file and refactor your test to use this new component. To do that, replace the call to document.outerHTML with a call to the render function, like this:
it('renders hello into the document', () => {
render(Hello, { name: 'world' });
expect(document.body).toHaveTextContent(
'Hello, world!'
);
});
This render function comes from the @testing-library/svelte package. Import that now, along with an import for the Hello component, placed at the top of the file:
import { render } from '@testing-library/svelte';
import Hello from './Hello.svelte';
Check that the test still passes with the refactor.
Then, add this third test, which verifies that the name prop in the component is being used to verify the output:
it('renders hello, svelte', () => {
render(Hello, { name: 'Svelte' });
expect(document.body).toHaveTextContent(
'Hello, Svelte!'
);
});
Run the test and make sure it passes.
Now, go ahead and comment out the render call in the last test. You might think that the test fails with an error saying nothing was rendered on-screen. But let’s see what happens:
Error: expect(element).toHaveTextContent() Expected element to have text content: Hello, Svelte! Received: Hello, world!
Hold on a second; is this what we expected? This test didn’t ever print out a Hello, world! message so why is the test expectation picking it up?
It turns out that our tests share the same document object, which is clearly not good for test independence. Imagine if the second test also expected to see Hello, world! rather than Hello, Svelte!. It would have passed by virtue of the first test running. We need to do something about this.
Ensuring the DOM is cleared after each test run
We want to make sure that every test gets its own clean version of the DOM. We can do this by using the cleanup function.
Create a new file named src/vitest/cleanupDom.js:
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/svelte';
afterEach(cleanup);
Then, insert that into the setupFiles property in vite.config.js:
const config = {
...,
test: {
...,
setupFiles: [
'./src/vitest/cleanupDom.js',
'./src/vitest/registerMatchers.js'
]
}
};
Now, if you run your failing test again, you should see that the Hello, world! message no longer appears.
Before continuing, uncomment the render call and check your tests are back in an all-green state.
Restoring mocks automatically
There’s one final piece of configuration we need in vite.config.js. Add the restoreMocks property, as shown here:
const config = {
...,
test: {
...,
restoreMocks: true
}
};
This is also important for test independence and will be important in Chapter 11, Replacing Behavior with a Side-By-Side Implementation, when we begin using the vi.fn function for building test doubles.
That covers all the configuration you need for the rest of the book. The next section touches briefly on some optional configurations you might want to consider.