Testing a SolidJS Component Using Vitest
Tags: solidjs, testing, webdev, tutorial
Published: 2022-07-08 15:17:00

Gear up cause in this week’s post we’re going to introduce Vitest to a SolidJS project and test a single component. 

Yes, I know that there is a [project template](https://github.com/solidjs/templates/tree/master/js-vitest) for it, but in order to understand better what it takes to include Vitest unit testing in your existing SolidJS project I will start from the bare minimum and add the required dependencies and configuration so that I will be able to run my first unit test with it.
Be warned - this is not a journey for the faint hearted, so I’ve learned along the way… ;)

Let’s go

***

I think the efficient way to go about this is to first introduce Vitest to the project, so I start with installing it

```bash
yarn add -D vitest
```

If you’re like me and started from the “simple” Solid template you should have a vite configuration file on your project root, `vite.config.js`, with the following content:

```javascript
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
 
export default defineConfig({
 plugins: [solidPlugin()],
 build: {
   target: 'esnext',
   polyfillDynamicImport: false,
 },
});
```
 
Vitest is supposed to read the configuration file OOTB and be able to work with it.
Next I will add a “test” script to my `package.json` file like so:

```json
"scripts": {
       ...
       "test": "vitest"
   },
```

And let’s try and run our tests…

```bash
yarn test
```

Sure enough we’re getting an error message that there are no tests found, which is actually great since it means that Vitest is set and ready. Let’s add our first test.

## The Timer Component
I have a Timer component with a very simple responsibility - it should display the time in a “mm:ss” format given a certain number of seconds, for example, if it gets 120 seconds it should display 02:00.  

This component is “reacting” to the application store’s `timerSeconds` and when that changes it knows to render the time string again. Here’s the code for the component:

```javascript
import styles from './index.module.css';
import {gameState} from '../../stores/game-store';
 
const Timer = () => {
   return <div class={styles.Timer}>{getDisplayTimeBySeconds(gameState.timerSeconds)}</div>;
};
 
const getDisplayTimeBySeconds = (seconds) => {
   const min = Math.floor(seconds / 60);
   const sec = seconds % 60;
   return `${getDisplayableTime(min)}:${getDisplayableTime(sec)}`;
};
 
function getDisplayableTime(timeValue) {
   return timeValue < 10 ? `0${timeValue}` : `${timeValue}`;
}
 
export default Timer;
```

I will now create the test for it, in the same directory, and initialize it with a dummy assertion just to make sure Vitest runs it well:

```javascript
import {describe, it, expect} from 'vitest';
 
describe('Timer component', () => {
   it('should assert some dummy assertion', () => {
       expect(1).toBeTruthy();
   });
});
```

Yep, it runs and passes alright.

Ok, so what we would like to do now is render the component and start testing it. For that we need the equivalent of the react-testing-library for solid, which is (sit down for this) [Solid-Testing-Library](https://github.com/solidjs/solid-testing-library). 
Adding it to my dev dependencies and moving on -

```bash
yarn add -D solid-testing-library
```

Now let’s try to render our component and see what hell breaks loose…

```javascript
import {describe, it, expect} from 'vitest';
import {render} from 'solid-testing-library';
import Timer from '.';
 
describe('Timer component', () => {
   it('should assert some dummy assertion', () => {
       render(<Timer />);
   });
});
``` 

Running the test and I get this error:

```bash
 FAIL  src/components/Timer/index.test.jsx [ src/components/Timer/index.test.jsx ]
SyntaxError: The requested module 'solid-js/web' does not provide an export named 'hydrate'
```
Was I too naive to think this will run smoothly? But of course.

It appears that this is a known issue and a suggested workaround can be found [here](https://github.com/solidjs/solid-testing-library/issues/10#issuecomment-1173329282). I will try that suggestion. My vite configuration looks like this now:

```javascript
export default defineConfig({
   plugins: [solidPlugin()],
   build: {
       target: 'esnext',
       polyfillDynamicImport: false,
   },
   test: {
       deps: {
           inline: [/solid-testing-library/],
       },
   },
});
```

And now when I run the test it fails on a different thing:

```bash
 FAIL  src/components/Timer/index.test.jsx [ src/components/Timer/index.test.jsx ]
TypeError: template is not a function
```

Seriously? 
Ok, at this point I’m going to look for the configuration the [vitest “template”](https://github.com/solidjs/templates/tree/master/js-vitest) comes with and understand what’s going on there. It seems that the integration with Vitest relies heavily on Jest ecosystem libs, which kinda sucks, but let’s roll with it.

I’m installing these dependencies first:

* @testing-library/jest-dom
* jsdom

After that I’m creating a setup file, named `setupVitest.js` which basically imports the `@testing-library/jest-dom`.

Next I’m copying the test configuration from the template but with a slight modification according to the instructions from [this GitHub thread](https://github.com/solidjs/solid-testing-library/issues/10#issuecomment-1173329282). 

The “resolve conditions” in the configuration is meant to instruct Vite to resolve exported modules in a certain way. In the configuration below it instructs it to be resolved as “browser” exports for the “development” env, which I can only assume means that it uses browser supported module exporting (if you know any different, please share with us in the comments)

And so our configuration looks like this:

```javascript
import {defineConfig} from 'vite';
import solidPlugin from 'vite-plugin-solid';
 
export default defineConfig({
   plugins: [solidPlugin()],
   build: {
       target: 'esnext',
       polyfillDynamicImport: false,
   },
   test: {
       globals: true,
       environment: 'jsdom',
       transformMode: {
           web: [/\.jsx?$/],
       },
       setupFiles: './setupVitest.js',
       // solid needs to be inline to work around
       // a resolution issue in vitest
       // And solid-testing-library needs to be here so that the 'hydrate'
       // method will be provided
       deps: {
           inline: [/solid-js/, /solid-testing-library/],
       },
   },
   resolve: {
       conditions: ['development', 'browser'],
   },
});
```

let’s run the test now…
Yes! It passes, but wait… we haven’t asserted anything meaningful yet. Let’s check that the timer displays “00:00” when it renders with no timerSeconds provided:

```javascript
import {describe, it, expect} from 'vitest';
import {render, screen} from 'solid-testing-library';
import Timer from '.';
 
describe('Timer component', () => {
   it('should render the timer', () => {
       render(() => <Timer />);
       const timerElm = screen.getByText('00:00');
       expect(timerElm).toBeInTheDocument();
   });
});
```
 
Yep, passes :)
(I obviously checked it failed to make sure my test does not “lie” to me)

Next let’s assert that once it gets 123 seconds it displays “02:03”. For this I’m gonna mock the store as if it was a simple object with a getter function on it (which, let’s face it, is pretty much what it is). What I like about this is that I don’t need to wrap my components in weird context providers just to access the store. That makes this a lot simpler in my eyes.

```javascript
import {describe, it, expect} from 'vitest';
import {render, screen} from 'solid-testing-library';
import Timer from '.';
 
let mockTimerSeconds = 0;
vi.mock('../../stores/game-store', () => ({
   gameState: {
       get timerSeconds() {
           return mockTimerSeconds;
       },
   },
}));
 
describe('Timer component', () => {
   it('should render the timer', () => {
       render(() => <Timer />);
       const timerElm = screen.getByText('00:00');
       expect(timerElm).toBeInTheDocument();
   });
 
   it('should render the timer according to the store timerSeconds', () => {
       mockTimerSeconds = 123;
       render(() => <Timer />);
       const timerElm = screen.getByText('02:03');
       expect(timerElm).toBeInTheDocument();
   });
});
```

Cool :) 

## Wrapping up
It was one hell of a struggle to get this configuration going and to be honest there is still work to be done to have Vitest a first-class-citizen in a Vite powered project. It also still bothers me that there are many Jest dependencies for a Vitest runner to do its work.
Having said that - it’s Vitest, my friends, and it’s blazing fast and that’s not something to take lightly.
Bottom line, a SolidJS project with Vitest test runner is up and running. Test away!

UPDATE:
Following a comment bt @lexlohr I've updated the configuration and you can find the rest of the details in this [update post](https://dev.to/mbarzeev/update-testing-a-solidjs-component-using-vitest-1pj9). 

If you have any suggestions on how this can be done better, and I’m sure you do, please be sure to leave them in the comments below so that we can all learn from it and evolve :)

*Hey! If you liked what you've just read check out <a href="https://twitter.com/mattibarzeev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">@mattibarzeev</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> on Twitter* :beers: