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.