UI Testing with React, Mirage, Jest and Testing Library

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Looks great! And the video is very pedagogical and instructive.

I have one nitpick, though, about the way you use Testing Library: While your reasoning behind using `getByTestId` to wait for the loader to disappear is sound, the concept behind Testing Library is to test apps the way the user uses them. They go so far as to discourage the use of `getByTestId` unless absolutely necessary, right in the docs. I'd use `getByText` for the loader, and `getByDisplayValue` for the item contents :)

๐Ÿ‘๏ธŽ︎ 11 ๐Ÿ‘ค๏ธŽ︎ u/f314 ๐Ÿ“…๏ธŽ︎ Nov 02 2019 ๐Ÿ—ซ︎ replies

Hey everyone!

I'm working on an API mocking library called Mirage. I made a video about using it with React + Jest so I thought I'd share it here!

You can learn more about the project on https://miragejs.com/. It's not fully launched yet but you can visit the /docs route and read the Guides to get a sense of how everything works. Folks have started using it, I'm just working up some last finishing pieces.

Would love to hear any questions you have! I'm also curious how people working on React apps are mocking out their APIs today (or if they mock them out at all, and if not, how do they develop + test against them).

Here are some links to the code for the demo

๐Ÿ‘๏ธŽ︎ 5 ๐Ÿ‘ค๏ธŽ︎ u/samselikoff ๐Ÿ“…๏ธŽ︎ Nov 02 2019 ๐Ÿ—ซ︎ replies

Just a heads up, EmberJS has a mocking library also called mirage so you may want to change the name to avoid confusion.

๐Ÿ‘๏ธŽ︎ 3 ๐Ÿ‘ค๏ธŽ︎ u/greshick ๐Ÿ“…๏ธŽ︎ Nov 02 2019 ๐Ÿ—ซ︎ replies

Does this project have any relationship to https://www.ember-cli-mirage.com ? If not the name could potentially be confused with it since itโ€™s a very common mocking service in the ember JS community.

๐Ÿ‘๏ธŽ︎ 3 ๐Ÿ‘ค๏ธŽ︎ u/Nikkio101 ๐Ÿ“…๏ธŽ︎ Nov 02 2019 ๐Ÿ—ซ︎ replies
Captions
today we're going to be writing some tests for this react application that is built using Mirage it's a to do application and we can see when we first visit it we see a loading message and then we see a list of to do's being populated over here in the console we can see that our app is making a network request to slash API slash to dues that Mirage is mocking out and that's where the data that's powering this interface is coming from we can check off to do's we can clear them we can create a new to-do and we can see that this interface is build kind of with an optimistic UI so kind of as we change this the UI updates immediately even though the network request is pending and so that's kind of how we've built this application and over here we can see kind of all the network requests that have gone out to Mirage and then we just have kind of a second route up here so if we visit it and come back we can see that all of our changes will persist to mirages and memory database for the life of this session now if we refresh this it'll start all over again with a clean slate and the initial three to Deuce so we want to write some tests for this application this is a create react app which comes with jest and we've gone ahead and installed and wired up react testing library which we'll be using to write our tests so right here we have our first test which says that it renders our App component without errors and over here we can see that this test is passing but this is not a very useful test so let's go ahead and write kind of our first meaningful test now in our dev environment we have these three to do that we're kind of starting out with in our mirage server and kind of the initial state of this app when we first load it is to show this loading message and then render these two dews now if we come back and pop open kind of our mirage server definition which is just right here in this file alongside our code we can see here that these are our development seeds and these are the three to do's that we're starting out our database seated with so if we just comment these out and come back and look at our app we'll see that after the loading message goes away we don't see any - duze now and we just see this message that says everything's done so I think that would be kind of a good initial State to get our app into within our test so instead of just rendering this we kind of want to reproduce this behavior here we want to render the app wait for the loading message to disappear and then assert that this everything's done message appears on the screen so we can do that using the helpers from react testing library so after we render we first want to wait for the loading message to disappear so we can go ahead and import wait for element to be removed and we can see here when we invoke this that this takes a callback function and so this callback function needs to return an HTML element and the way we can get the element itself is by using the return value from render here so we can go ahead and do structure get by text which is a helper here and we can use that to go ahead and query our Dom by loading and we'll see if we hover over these waiters from react testing library they actually return a promise so we need to await these waiters which means this test needs to be an async test and so if we save this and come take a look at our console output we'll see that we're actually getting an error now and it's telling us that the network request failed and that's because you know initially when our app loads it makes a get request to our API endpoint but in our test we're just rendering the app and Mirage isn't intercepting any requests here we can see over here in our kind of development bootstrapping file the index.js file this is where we import our server and call make server here but in our tests we haven't done that yet and so it's usually a best practice when working with Mirage to share a server across development and testing that way you don't have to do any of this routing logic or any models or factories you use between the two environments and this way that we're exporting it is just kind of a convention we're just exporting a function here and we've added some options to this function to specify the environment which defaults to development but in our test we're going to use the test environment so let's come here and get Mirage working in our test we want to call make server here and we can pass in an environment of test here and this is just gonna stop Mirage from logging by default and to stop it from adding a delay to keep our tests fast but all that's kind of configurable so if we move this down into our test here go ahead and make our Mirage server and then render our app now when we come back to our tests we see that it's passing so it indeed is rendering without errors it looks like and we can actually go ahead and inspect the state of the Dom after our loading screen has been removed here if we just get the container from this render function and then we use the pretty Dom function from react testing library pass our container in and we just go ahead and console dialogue that so if we save this we'll see that just is going to print out kind of a snapshot of the Dom at this point in the test and there we can see home and about our two routes the to do is label and then here we see our message everything's done just like we would expect looking at an app that doesn't have any two Do's so let's go ahead and add an assertion that this is actually here because this is what we expect so we can do that using expect and we can get by text everything's done and we just expect that element to be in the document so let's save this and it looks like everything passes and if we were to you know change this to something that's wrong like we typo here we'll see we get an error that it was unable to find an element with the text with our typo so this is in fact working mirage is actually handling this response here and now we have kind of a meaningful test that says it shows a message when there are no two deuce let's write another test we want to make sure that it shows existing two deuce and so this is going to be the case that we started out with if we go ahead and drop our development seeds back in and we look at the app when we first load it we see three - duze in the list here and so that's what we want to test next and so here we're kind of copying this setup code for our server every test is going to expect the server to exist since the app expects the server to exist so we can go ahead and extract this out into a before each block which is a feature of just and we actually want to make sure to clean up our mirage server after each test runs and so we'll go ahead and drop and after each here and we can just use a variable but server make server returns the server and then after each we can call server dot shutdown so if we get rid of this and let's use only to run our first test again save all that come back looks like we still have a passing test so now let's write our second test here so the cool thing about working with Mirage and our tests we like this is that we can actually manipulate the server directly in a test and so by default our server starts off as empty that's one of the things that this test environment does it ignores the seeds here because each test is basically zone scenario where you can see the database in exactly the state that you need for whatever behavior that you're testing these are kind of our development only seeds and so that's one more reason we want to customize the environment of our Mirage server during testing so back in our tests we kind of start off with a minimal version of our API server here but in this case we want to start off with some to Do's so we can use server dot create to do and then we can go ahead and pass in a text here test to do and that will end up in our mirage database before we render the app such that when we render this and our react app makes an API request to the network Marah should respond with these two Do's so now we should have a failing test because everything's not done and indeed we see an error here shows existing to do is unable to find element with text everything's done and if we look at a snapshot of the Dom here we see two do's and then here we see our unordered list and we see our first list item here which is kind of hard to do we see the checkbox here and then we see the text field with the value of test to do so looking over in our app that's basically this part of the Dom right here and so you can see we wrap each one of these list items with a data test ID so far we've been using these selectors yet by text and if you take a look at this you know there's a bunch of selectors that we get from react testing library and one of them is actually get by test ID test IDs are a good way to just add a slight bit of abstraction to your testing code you know you can imagine that we might change how this is presented over time and if we change the text content of this or the value of this in a small way we could break a lot of our existing tests the test ID is just a small abstraction that says this element being present is really what we care about and so we like to use these test IDs here and so instead of expecting this message to be in the Dom we actually want to query the Dom for all the to Do's in this case we have three different ones but over on our tests we're only creating one so we can actually go ahead and use they get all by test ID this is kind of the versions of these selectors that you use whenever you're querying for a set and this is just going to be to do and again that's coming from this attribute on our list items right here and those are our two do's and we expect that to do zeng --the to be one and now we have passing tests and of course if we were to create another to do now we see we have a failure here so if we uncomment this only we now have two passing tests and if we pop over to our actual component here and we search for loading will see that this message also has a data test ID of loading so if we wanted to we could come up here and say get by test ID and we're just going to use that attribute up there and then also get by test ID right here as well loading save that and now we have kind of a passing suite again this is just an approach again to reduce the brittleness of our test suite now we just need to make sure that there's some element that has this test ID in it but if we were to say change what this message look like from a paragraph tag to maybe something with an image or something like that all we need to make sure is that this is present in the Dom in those tests will pass there's one more change we can make to the second test here we are actually passing in the text of this to do when we're creating it and we're calling this test to do and again if we were to grab the container from this render function and go ahead and log out a call to that pretty dumb function passing in the container we can see here that that is in fact showing up in the Dom but this test is not so great because we could change this to some gibberish save it and come back and our test is still going to pass so this detail right here of the actual text of the to do is not really needed for any of these assertions and it also doesn't really help anyone reading this test to better understand kind of what we're trying to test here which is just that these show up in the Dom so we could actually just delete this completely this whole set of attribute overrides here and just let Muranos create kind of a basic to do now we can go ahead and log out server dot DB to dues and this is going to give us kind of everything in mirages server and if we come here we'll see that this is kind of just an array with one object in it with the auto assigned ID of one and then if we look at our Dom container will see that this to do of ours is now blank so this is better in the sense that there's no extraneous information in this test but now it's kind of worse in the sense that this is not really realistic if we were to come back to our server over here and kind of do this same thing with our dev seeds then we can see what this looks like and you know this is not really the best kind of setup for a test even though it's just a testing environment this is not really realistic so mirage has a solution for this kind of new problem that we've introduced by trying to keep our tests you know small and relevant and that is factories so we can define a factory for our to do by defining kind of a top-level key here called factories and we're going to do one for the to do model and we're going to make this a Factory and here we can specify default values for our to Do's so we could say the default value for texts is mine to do and there we see they all have mine to do on them or we can make this a function that takes in kind of an index and returns to do with the index and that index starts zero so maybe we'll add one to it so now we have this simple way to get kind of more realistic looking default data from our server just by calling server dot create and over here kind of in our more curated development seeds we can see that to do is also have an is done property and we can definitely imagine that omitting that could cause problems down the road so that would also be a good thing to add to our Factory is done and we can just default that to false and so now when we call server create we kind of get these minimal versions of the to do's that are more realistic and valid for our application now in development we might still want to use these so we can go ahead and put these back but now over in our test we're just calling create to do here and if we run this test now we can see where we're logging the two dews from Mirage we have kind of this full object here with an ID text and as done property and that's coming from this log right here and we can also look at the Dom snapshot and see that our input has a value of two do one so I think this is a improved test because some of the extraneous details were removed and if we wanted to kind of create that environment that we typically have in dev we could go ahead and easily create three right here we should get a failing test because we have three and we only expected one and we expect there now to be kind of three in the DOM and now we have a passing test so I think this is a little bit improved from what we had before let's go ahead and write one more test and this time we're going to be testing that it can create a new to do so hopping back over to our development scenario if we comment these out and we look at the app we kind of load up a clean slate here and we can type in new to do in this input field we save it we see that our app makes a post request here and our UI updates so let's go ahead and write a test for this feature will start out Mirage with kind of an empty back-end here and we'll go ahead and render the app wait for the loading screen to go away and if we wanted to we could go ahead and drop an assertion right here that says we expect everything's done to be in the document and the next step again kind of replaying our behavior here is to input some text into this field so we can go ahead and inspect this and we can see that this is part of a form that has a data test ID of new to do form so why don't we go ahead and get the form here new to do form equals get by test ID need to do the form and we want to type into this form we can do that using the user event API user event comes from another import we can import user event from testing library user event and what this lets us do is call dot type passing in an input here and our new to do text now the input we can go ahead and get that from our form our form is the element here we can go ahead and call query selector off this and we'll just look for the input of type text so we get the form we type new to do into it and if we want to check our work again we can go ahead and call pre Dom on container go ahead and log this out get container here and take a look so we see the everything's done message and up here for our new to do we actually do see the value has been set and we can just change this to not match the placeholder walk the dog and make sure everything's wired up there we go so next we want to submit the form and we can do that using fire event which is going to be automatically imported here from testing library react and we want to fire the submit event on our form new to do form and so again we have an optimistic UI here that means when I type in walk the dog and hit enter this is going to be immediately appended to this list and in the background we're gonna see a saving indicator right here while on our server in this case our mock server Mirage but in production it would be a real server handles that request but because the UI updates immediately we can go ahead and assert that this new to-do item is actually in that list so we can use the same code up here go ahead and grab the - dues from the Dom at this point we only expect to dues that length to be one and we can also make an assertion against the actual to do itself so we can say expect to dues the first one and again we can go ahead and query selector here for the actual input and if we come look at the app these to-do items are this kind of wrapping Li element and there's this input right here with type text that has a value that we're interested in that we would expect to see from our need to do so we can go ahead and just query for input type text and we can assert that the value there that should be equal to walk the dog so let's save this and check out our test looks like it passes we can also assert against the checkbox here because when we set the checkbox to true if we were to open up kind of our component profiler here and find one of these two Do's we see that the to do object has an is done property and that's what's kind of mapped to the checkbox so when we create a new to do you know this is an important part of the app type something in here and hit enter we expect is done to be false but from the perspective of the user we expect that checkbox to be unchecked so we can write kind of another expectation here go ahead and grab the checkbox and assert that checked we actually expect that to be false and there we go passing test so this is pretty cool we're waiting for our app to load we are finding the new to-do form and submitting it and then we're asserting that our UI updates now one thing we we forgot to assert was that after we kind of save something this new to do field should be cleared out so we can go ahead and add that because that's another part of the behavior that's important we would expect the new to do forum query selector input type text value to be the empty string now we have that aspect of the feature covered so we're doing a good job covering the UI aspects of our react application but we're not really verifying that our app is sending over the right data you know whenever we create this we kind of see the saving indicator up here we also see the post request and you know arguably those are the most important parts of this feature here because that's what actually saves the to do so how might we cover that behavior in our tests well sometimes it makes sense to make assertions against the actual network request that your react app is making so in this case we would assert that the payload looks something like this and that this outgoing post request actually happens but with these kind of UI tests that we've been writing I like to keep both the setup and the assertion part of our tests basically as high level as possible and so in this case you know if we were just talking about this application with a product person and we said you know when you type in test and hit enter you'll see a saving indicator and then it'll go away and that will create one to do in the server I really like my assertions to kind of match that level of abstraction as much as possible so what might that look like kinda in our test here well we could do exactly that just like up here where we wait for kind of a loading indicator to be removed from the Dom after we save this we could go ahead and say now let's wait for our saving indicator to be removed from the Dom and if we pop back over to our to do zap here and we look for saving we'll see here that if we have this saving state is true we're gonna render this SVG that's kind of a cloud icon you see here whenever we make a mutation that's indicating that that the request is being processed and this SVG has a data test ID of saving so we could go ahead and say let's wait for saving to be removed and that's going to make sure that that saving indicator is rendering so we can go ahead and save this come look at our test suite and we see that it passes and you know if we refactor this app and someone forgot to actually have this saving indicator in the new version of the app we're actually going to get a failure of our test suite because jess is expecting to find that in the DOM and then wait for it to be removed but it doesn't see any more so just with that we are covering that behavior we're making sure that this element is actually in the Dom after we submit the form and that it's removed but this still doesn't cover the actual network request that's going out and in order to cover that we can just make assertions against the state of our mirage server directly so we can just say we expect Server db2 dues length to be one because we're starting out this test with no to dues after we create one we would expect one to be kind of in our mirage server so if we save this we see we have a passing test and again you know if we wanted to be extra confident here that we have everything wired up we could add some expectations to the beginning of our test you know some people like to do this when their test is a little bit more complicated and we'd expect there to be 0 up here and there we see that it's actually true we can also add an expectation that that first to do in our server has some of the correct data so if I were to just save this we should see an error because that to do shouldn't be one it's actually an object and right there we see what's in mirage is this pocho with an auto assigned ID of 1 is done as false and the text is walk the dog so we could just say that we expect that to do stack to be what we typed up here in the input which is walk the dog so now we have a passing test we're covering our mirage behavior we're covering our optimistic UI here we're not using this so we can go ahead and get rid of that and we have a lot of aspects of this user flow covered now in this test now again some people like to have kind of early expectations here we don't need those if we don't want and we can also go ahead and get rid of this only and make sure all of our tests pass if you're familiar with triple-eight testing you'll see that kind of a test we wrote tend to follow that pattern Triple A testing is the idea that your test should follow an arranged an act and an assert pattern and so you can see we're kind of arranging the mirage server up here and then within each test we have we have these steps as well in the case of this test where there's no to do's we don't really have an arrangement we just act which in this case is just rendering the app and then we assert that this message shows up down here we arrange the server by creating three to do's here sometimes you'll also hear this step called assemble and so after that assembly we go ahead and act which again in this case is just rendering the app which from the user's perspective is visiting that homepage URL and then we make our expectations our sessions and then down here we also don't really have an arrangement step our actions are really this visiting the app filling in the form and submitting it and then we have our expectations down here and you could even argue that this kind of waiting for the saving indicator to be done is really more of an action and we could move this up here and group all of our expectations like this although this doesn't really cover the optimistic UI part of it we could also split these into two different tests one that tests the UI and one that ensures that after that saving indicator goes away we actually have the new to do in our Mirage server but these are mostly stylistic concerns and in general triple-eight testing is a really good practice to follow and it suits writing tests like this with Mirage very well so now we have test coverage for these three aspects of this application I'll put a link to the repo with more tests covering things like editing it to do so after we have kind of a new to do that's been saved you know we can edit it like this we can tab out and it saves we can see Mirage is handling a patch request we can check it off as done this updates as a result of that so we could assert against that part of the UI we could also assert you know when we visit the about page and come back that our changes have been saved that this clear button actually calls a delete request to each one of these two dews and so we can use Mirage and this automated test suite here to just completely cover all of this behavior and make sure that we don't regress on any of that as we continue working on this app so that's it that was a brief introduction to writing UI tests with Mirage just and react testing library I hope you enjoyed it
Info
Channel: Sam Selikoff
Views: 5,280
Rating: undefined out of 5
Keywords:
Id: 3taVrGZVCr8
Channel Id: undefined
Length: 31min 55sec (1915 seconds)
Published: Fri Oct 25 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.