I’ve been doing automation since 2012. This is the era where the CI ecosystem became more approachable for companies which did not have dedicated teams. I’ve worked on some amazing projects, most of them were for big companies but we never managed to crack down on an agile CI flow that will give us the best results in a small effort.
The company I worked for was invested in the Drupal ecosystem at the time. Drupal provided tools to check the code but not the site as it is. Just like any open-source based communities – tools came from within the community, and we got our superhero: the migrate module. That module allowed us to migrate data from any source into our system. We had dummy data in CSV files and our setup script for the local environment migrated that data. That allowed to for any developer to have the same data, we set up data close to what our client needed thus reduce bugs when the customer started to write content.
But how can we test it? We found a tool called behat that simplified our interactions with selenium, and we could write test cases in plain English that would eventually translate to selenium commands. But how do we CI it? At the time, Travis CI arrived, and we would set up a LAMP environment (way before docker was a thing as of today). Yes, that was edgy. It was so edgy that if you tapped our back, we would fall off a cliff.
However, not all people agreed with that. But, they said it was time consuming. Sure, this approach is correct for companies that rely on projects and can copy paste the flow. But, for small teams this is too much. Nobody know what tomorrow will bring and investing that time for a product that might pivot is terrible.
As time went by, web apps moved from developing full pages into components-based architecture and even got atomic design. We also got tools to test our react components using jest. The problem is – testing component is kind of covering 80 percent of the logic.
Introducing – component testing
When unit testing a component, we are testing it in an environment which mocks the browser environment – where the component will live. If we really want to test the component, we need to test it in a browser but we kind of looping back to the original problem: setting up the environment for our application is too much time consuming.
Here is where our true hero of the story arises – Storybook. Storybook allows us to interact with the component and only the component within an isolated page. It’s an absolute win:
- First we can share components with the design team thus allow them to keep the design consistent in tools like Figma
- Secondly, we can develop the component isolated and independent from other components. This ensure our component is small.
- Thirdly, quick development – without reloading all the application implements the component alone ensure a quick cycle.
For this example, let’s set up and small project with vite, react and storybook.
Step 0 – creating our dummy site
I created a small react app, with three components. You can clone it from here (enter url to repo). We have a list of foods I love; we show each food with some data in a card, and we have a small rating from 0 to 5 so that we can rate the food.
First, let’s do some component testing. Vitest is easy and fun. We can approach component unit test in two ways:
- Click on elements and wait for something to happen
- Snapshot testing – this will ensure that our HTML format is correct.
Though snapshots give us validation on the structure of the component’s HTML it does create more noise through files that we need to maintain – as our code changes the snapshot files are changing as well. This will lead to a bigger pull request (you do use pull requests, right?)
Clicking on elements is a classic approach – clicking and verifying something has changed. For example, look at this PR (link). You can see I added some unit testing that verifies that we can see the elements, we can click on them and assert the stars counter have changed.
Now, let’s introduce a bug. In this PR I added an element that covers all the other elements with some absolute position. The unit tests are passing but when trying the component our self in the browser, the native environment, it’s not working.
Step 1 – storybook in action
Let’s install storybook and set connect the components to the storybook. This blog post was written at the time where storybook 8 is the latest version. Adding storybook is pretty easy. Just run the command from the home page and then some magic happens and you get predefined stories.
So I organized the project a bit – moved components to dedicated folder, with their own CSS and now we can add storybook for each component and not get lost with files all around. You can have a look in this pr (link to the pr) how I did it. I also commented out the CSS that broke the component functionality.
Now, let’s record some tests
** recording some tests **
Cool, now let’s add the faulty CSS block and see if your test was broken or not:
I can say one thing – 😮💨. We can see that our small test helped us verify the component works as expected.
Let’s take it higher!
To sum it up, we saw that setting up storybook was easy and integrating into it is not a hustle. But what can we do to take this to the next level? Large project, which are built correctly, write small components and our E2E can be break down into different layers:
The first one can be unit test for the business logic. It’s supposed to be small and concise.
Next, in the second layer we can record a test for each component in storybook, run them in parallel.
The third layer can have a quick and shallow flow to verify the components appear on the screen. Because we tested all the edge cases in the storybook test we are covered.
The fourth layer will test the heavy flows against our app with a backend, similar to our production environment.
Sure, we are still going to set up a backend and the frontend application with dummy data, but this will happen at the last layer after all our faster tests went OK. If we broke something the logic or the CSS of the component’s we will fail earlier and faster leading to fast iteration in our CI.