How to Use React Testing Library and Interactions in Storybook
Let's write some interaction tests directly in Storybook!
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the Storybook for React Apps course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Storybook for React Apps, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:15] If you ever wrote tests for your component using React, Jest, and testing library, then you know how it goes. You render your component in isolation, having to mock all the necessary things for it, and then you simulate user behavior using testing library, and then you use Jest to assert things.
[00:16 - 00:26] But those tests, they run in JS DOM, which even though it's fast, it's not a real browser. And if there's a failure there, it's really hard to debug because you don't have a visual overview of the failure.
[00:27 - 00:46] In this lesson, you're going to learn how to run better interaction tests directly in the browser using Storybook. We're going to be talking about a play function, which is a new annotation for stories, a browser compatible version of testing library in Jest that can execute in the browser, a new atom for Storybook called "Adam Interactions" and a Storybook test runner to automate tests.
[00:47 - 00:50] Let's get to it. First of all, what exactly is the play function?
[00:51 - 01:01] So let's start by visiting the restaurant card over here and open the stories. In going to the default story, we can add a new annotation which is called the play function.
[01:02 - 01:15] And the play function is an asynchronous function that has context as an argument and executes after the component is rendered. So for instance, the context is returning and we can destructor from it a bunch of very interesting things about that story.
[01:16 - 01:27] So what the things that matter to us is the canvas element and the arcs. So what we can do here is we can just console log both the canvas element and the arcs and save.
[01:28 - 01:44] When visiting Storybook with the browser dev tools open, we see first of all that our play function has access to the arcs of the story as well as this canvas element which is essentially the wrapping element that wraps the story. And that's going to be very useful for us really soon.
[01:45 - 02:01] And basically because the play function executes in the browser, what we can actually do here is we can use testing library to write automated interactions which will happen directly in the browser so we can visualize them. And we can write assertions using jest to make sure that those interactions work correctly.
[02:02 - 02:07] And for that to happen, we just need to install special wrappers that Storybook provides for us. So let's do the following.
[02:08 - 02:14] First of all, let's stop Storybook right now. And then let's install a couple of the fantasies that we need to use right here .
[02:15 - 02:31] So we can do yarn add developer dependencies and then add storybook slash testing library storybook slash jest and then storybook slash add on interactions. So both of these are the browser compatible versions of testing library and j est.
[02:32 - 02:46] And then we have a new add on which will help you visualize those interactions as they happen in the browser. And once this is done, let's go to the storybook folder and open main T s and let's register the new add on.
[02:47 - 02:57] So the new item is called at storybook slash add on interactions. And this add on contains a debugging functionality which is not enabled by default in storybook six point four or six point five.
[02:58 - 03:08] So let's enable it by using a features field in the main JS and then it's called interactions debugger and set it to true. So now we're able to run storybook again.
[03:09 - 03:14] So we type yarn storybook. So let's close the developer tools.
[03:15 - 03:23] And if we open the add on spennel, we should see that there is a new panel called interactions. The other interactions will be visualizing every single interactions that we write.
[03:24 - 03:29] So let's start by adding some tests to the restaurant card. One of the things that the restaurant card has is an unclick action.
[03:30 - 03:42] So if you click on it, as you can see, even the other actions will show us that this on click is being called. So let's try to write some tests just to make sure that once you click in the component, the on click handler is being called.
[03:43 - 03:55] Let's go to the story file for the restaurant card, which is under components, restaurant card, restaurant stories. So in the default story, instead of writing this console log, let's just delete it and start by writing actual interactions.
[03:56 - 04:05] So the first thing we noticed was that the canvas element is the element that wraps our component. And there's a helper function from testing library that allows you to query elements within an HTML element that you're passing.
[04:06 - 04:16] So the first thing we have to do is we need to import from storybook slash testing library. And then the helper function is called within.
[04:17 - 04:20] And for the user interactions, we're going to use user event. So let's just import it.
[04:21 - 04:32] So going back to our play function, all we have to do is create a canvas, which is within the canvas element. And from this canvas, we can essentially query elements from it.
[04:33 - 04:41] So for instance, we could do get by test ID. And then we can pass restaurant dash card, which is the test ID of the element.
[04:42 - 04:52] And then this element, we want to interact on top of it by using user event dot click. And some of these interactions and assertions, they actually have to be awaited .
[04:53 - 05:01] But the nice thing is that the Islin plugin is going to tell us whether we do need to wait or not. So then you can actually fix this just by clicking here.
[05:02 - 05:07] And there you go. So now we need to know whether the on click function has been called or not.
[05:08 - 05:19] And because we're using both add on actions and add on interactions, basically, any of the actions here, they are turned into just spies automatically. And what you can do here is you can expect.
[05:20 - 05:28] And over here, we actually need to also import this from storybook just. So let's just go ahead and do that.
[05:29 - 05:33] And it's called expect. And we essentially can expect.
[05:34 - 05:45] Arts, because we do have access to it from here dot on click dot to have been called. And expect is also an asynchronous function.
[05:46 - 05:53] So we have to await it as well. So once we save this and go back to our stories, the interactions panel should be properly updated.
[05:54 - 05:58] And now you can visualize the interactions over here. And you can see that they're passing.
[05:59 - 06:06] And that's great. So the components still rendering correctly, but now we are making sure that once we click on it, the interactions are happening correctly.
[06:07 - 06:14] So let's go ahead and do the same thing. But for the closed story, because the closed restaurant card should not yield any action.
[06:15 - 06:28] So let's go ahead and just basically copy this and put here and then rename this to closed. And then instead of expecting the arcs on click to have been called, we expect not to have been called.
[06:29 - 06:41] So when we're back to our stories, we see that the element is being clicked and the action is not being called. And just for experimentation sake, let's, let's just do like, we expect this to have been called.
[06:42 - 06:49] So once the interactions we run, we will see what happens if there's a failure. And over here, this is pretty much the same feedback that you get from the CLI.
[06:50 - 06:53] But now you get in the browser. And then you can see like, Oh, the click is happening correctly.
[06:54 - 06:59] However, the expectation is not. And then that will help you debug much better than using the CLI.
[07:00 - 07:07] And that's great. Now we are still rendering our components correctly in our stories, but we're making sure that the assertions are working fine.
[07:08 - 07:10] So let's undo the change we made. So this is correct now.
[07:11 - 07:25] And also, given that we now are able to run interactions directly in the story, what we can actually do is we just go into the restaurant detail page. And because we know this is a very interactive page, so you click on the food item, it opens a model, we can create a new story with that interaction.
[07:26 - 07:33] So let's go ahead and do that. In the code, we go into pages, restaurant detail page, restaurant detail page stories.
[07:34 - 07:44] We just create a new story based on success. So then we can use the same parameters of success and creates the, let's say, with model open story.
[07:45 - 07:54] So let's just move that below here. And then the with model open story, parameters are going to be pretty much the same as the success dot parameters.
[07:55 - 08:05] And then the with model open story, it's going to have a play function, which is a synchronous. And then we get the canvas element.
[08:06 - 08:14] So we follow pretty much the same things we did before. So we have a canvas, which is within, and that is coming from storybook testing library.
[08:15 - 08:21] And then we pass the canvas element. And then let's say that we want to click on the cheeseburger element right here .
[08:22 - 08:40] And because the page has a loading state before it renders first, even though it's pretty fast, you don't see it, it still takes a couple of rendering cycles to render this element. And because of that, we can't just do get by text cheeseburger, but we can achieve this by using canvas dot find by text.
[08:41 - 08:46] And then we just pass a rejects, let's say cheeseburger. And we ignore the case.
[08:47 - 08:54] And testing library is going to use a mutation observer in order to keep trying to get this element until it appears on the screen. And that is an asynchronous operation.
[08:55 - 09:07] Let's save it in a, in a const right here called, let's say food item, which awaits this thing. And then we need to use user event also from storybook testing library dot click to that food item.
[09:08 - 09:15] And then we also need to await this interaction over here. And then lastly, we want to make sure that the model appears on the screen.
[09:16 - 09:22] So if we save this interaction right now, and we go back to the browser, we should see a new story here. And then that should already open the model for us.
[09:23 - 09:33] And if we open the interaction span, we see that the steps are running correctly. However, we also want to make sure that the model exists because clicking in the element doesn't mean that the model appears.
[09:34 - 09:41] So let's go back to the play function and write an assertion using expect. And let's say canvas dot get by test ID.
[09:42 - 09:45] And then the model has a test ID of model. So let's just use that.
[09:46 - 09:55] We also need to await the expect and then we do to be in the document, let's say, and expect has to be imported from storybook. Just.
[09:56 - 10:02] So once we save this and we go back to our browser, the interactions will run again and the assertion is happening. And there you go.
[10:03 - 10:11] You should now have a new story that both shows a model at the same time as tests if the model is present. So in the future, if there's any regression, the story will fail and tell us the air.
[10:12 - 10:33] And actually, one of the reasons why we're awaiting every single one of these steps is because the storybook add on interactions provides us with a special debugging functionality, which allows us to go back and forth in these interactions. So what we can actually do here is go back in these controls and just undo and redo all of the interactions that are happening.
[10:34 - 10:37] And this is very, very useful for debugging. So you can go all the way to the end.
[10:38 - 10:47] You can go all the way to the start before even running the interactions. And you can also click on this button on the top, which will remount the component and rerun the interactions.
[10:48 - 10:53] And that's great. We're now able to test our components by using our favorite tools directly in the browser things to storybook.
[10:54 - 11:03] This opens a huge amount of possibilities. But having to manually check whether the tests have passed by visiting each story can become exhausting pretty easily.
[11:04 - 11:09] Storybook has a test runner that turns all stories into automated tests, which is what we will be setting up next, so I'll see you then.