The other day I was building a storybook page component and I was having issues where the page had a useEffect hook that hit an api from the dev environment to populate the state. There was a few issues with this because the data I was getting back needed an Id so it could a lookup in the database to find the record relating to the Id.
This to me gave me a few red alerts because I couldn’t make my story look the way it would on the website. This blog post is about how you can work around that to enrich your stories and make them more effective when your components work with API’s.
Why shouldn’t you just use the dev API for storybook?
Maintaining page states
Very hard to maintain multiple page states as you have no control of the data that is coming back. So if you wanted to display an error from that api, you can’t do that with ease because the backend api controls what comes back.
Dependency on the database
The record could get deleted which will break your story. If you have visual regression tests it will be picked up but if not you could have a broke story for a while depending on how often the story gets worked on.
Needs real data for the backend to consume
Backend API’s have special requirements with what data you send in the body but all we care about is the different types of responses our page handles and thats hard to do when we have no control over it.
Storybook middleware
Storybook has a feature where you can setup API calls which can be used in your stories. Under the hood the middleware is an expressJS server so the syntax is really simple, for example:
.storybook/middleware.js
const express = require('express');
const bodyParser = require('body-parser');
const expressMiddleWare = router => {
router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json());
router.get(/api/get-orders/:orderId, (request, response) => {
if (request.params.orderId === 'error') {
response.status(500).send('Something broke!')
}
res.send({ data: { text: 'hello world' } })
})
};
module.exports = expressMiddleWare;
And thats it you have an api setup in storybook. If you hit that relative path on localhost:9001/api/get-orders/123 it will respond with a success and if you send localhost:9001/api/get-orders/error you have forced an error state in your api so you can create two stories for your page component. Here is an example to how it would look in your story.
export const withSuccessfulOrder = () => <Comp orderId="123" />
export const withErrorOrder = () => <Comp orderId="error" />
Handling environments
Some websites tend to have multiple api environments such as dev/staging/prod and they normally have an environment variable to define the api domain. So locally the frontend could be hitting the dev environment and then when that code goes into prod the api url is now the prod domain. It normally looks like this in your code..
fetch(`${API_URL}/get-orders/${orderId}`).then((res) => res.json())so on...
In storybook when we build our stories together the default env would need to be changed to api or whatever url you have setup in the middleware so the code replaces API_URL with our /api. Easy way to do this is by setting the env when you run the storybook build/serve command.
API\_URL=/api storybook build
Then the code that references that env var will be /api and the example about will be /api/get-orders/${orderId} .
Conclusion
That is pretty much it! We can now start writing more efficient stories that are a lot more dynamic and help us extend our development before switching over to testing it in the website. The benefits to mocking the apis in storybook are:
- The state is in our control so we can make our stories more accurate by sending the data we expect to come back from the api.
- It won’t change, the mock api won’t fall over or change behaviour like it could in a backend api.
- Once an api contract is in place with the developer building it, you both can work at the same time with no dependency on each other.
- Visual regression tests are more accurate.
Top comments (2)
Hi, I found this helpful, thanks for sharing! Just thought I'd mention there are some syntax errors (missing quotes around the URL) and undefined variable
res
vsresponse
.Thanks, again!
Really useful article Matt, exactly what I was looking for and took me ages to realise you were the author! Hope you're well!