Video Summary and Transcription
This Talk discusses testing V3 applications with Mock Service Worker, which is a library that allows simulating server responses in tests. It covers setting up Mock Service Worker by creating mock API responses and connecting it with the application. The Talk also explains how to write unit tests for asynchronous components using Vue's suspense component. It demonstrates how to test components that interact with APIs and handle error responses. Additionally, it mentions the testing library for components without API calls and emphasizes the importance of testing component interactions and API integration.
1. Testing V3 Applications with Mock Service Worker
Hi there. Today I'll talk about testing V3 applications with mux-serviceworker. We'll explore how to use V3 with mux-serviceworker to improve code quality and testing. Testing V3 applications is crucial for catching bugs early and increasing confidence in code. Mock Service Worker is a library that allows you to simulate server responses in tests, making it useful for large applications with API interactions. It's also great for running unit tests in a continuous integration environment. Let's look at an example using mock service worker in a Vue 3 app to mock API responses and test components that interact with the API.
Hi there. I want to tell you today about testing V3 applications with mux-serviceworker. I'm Lizzie. I'm a frontend architect at Storyblock. You can find me on Twitter, but most importantly, you can find all the examples that I made for this talk in this GitHub repository.
And let's dive right into it. So this talk is based a little bit on Harry Potter. And we will figure out how we can use V3 together with mux-serviceworker to improve our code quality and to test in a better and more integrated way.
So why is it important to test V3 applications? It's really crucial to perfecting your code, to making sure it's doing what it should. It can really help you to catch bugs before they go into production, because if you catch a bug in production, it's a lot more expensive. So the earlier you catch the bugs, the better. And it can really help to increase the confidence in your code and the code you're writing and also the code you're changing.
So what is Mock Service Worker? Mock Service Worker is a really nice library that you can install into your application and it allows you to simulate server responses in your tests. So you can create a mock API and you can test how actually your application is handling the responses of the server and also handle edge cases of server responses, which might return errors. Why would you use that? It's really important in large applications that have a lot of API interactions. So many large applications like Storyblock, they have a lot of API interaction and you want to make sure that all these different components, they're actually working as they should work with these different responses you can get from the API. It's also really nice if you run your unit tests in a continuous integration environment. For example, you run your unit tests in GitHub Actions, and inside of that continuous integration environment, you cannot call the API. So you need to mock the API and run the unit tests with actual mocked responses. And that's where using mock service worker is really nice.
All right, let's look at the example I built and how we can actually set it up. To start using mock service worker, you can start using it in a view 3 app. You need some kind of testing environment. So in this example, we'll be using vitest, but it works with Chest just the same. And you need mock service worker, which is the library that is in the core of this talk. So the example I built, it's showing my magical beasts. We have a headline. We have some cards with the USB stick, and we can click on those cards and see some more details of the magical beast. And all the content here, it's loaded from an API. So I have a story block space where I set up some content.
2. Setting up Mock Service Worker
I have a page that has the headline and the different magical beast. I fetch the actual content from the Storyblocks API to show the details of the magical beast. To mock the API, I create a mock directory with JSON files that replicate the API responses. Then, I set up the mock service worker and handlers. This involves creating a setup file and connecting mock service worker with YDist.
I have a page that has the headline and the different magical beast. And then I can also see what's actually returned there. So through the API from story block, I get the actual content that I can then use to show my content, the same for the more specific pages. So I have a magical beast and then I have some content here that shows all the details of that magical beast.
All right. So let's start. The first thing for making a mock service worker setup is that you actually need some kind of API, you need some kind of asynchronous way of interaction. So in my example, I have a component that fetches, just does a simple fetch to Storyblocks API and that has all the responses. And then from that response, I build the actual page that we see. And the important part here is that now we need to mock the API. So our first step is to actually create a mock directory. In the root of our project, it could also be somewhere else, but I will do it in the root. And then we set up the mock service worker.
So what do I mean with a mock directory? If we look into the example, we have a folder there that's called mocks. And then we have two JSON files. So we have the beast JSON, that's the start page. And then we have the niffler JSON, that's a mock of the actual, more specific page. How can you get those JSONs? The simplest thing is to have your application, and then check in the network what is actually being returned by the API. So if we reload that, my mock here is basically this request and this response we have here. So the story, I could just copy that and paste that here into my beast JSON file. So this one is just a copy of what I had on that page. So that's really the first part, to have some mocks that can be used by the mock service worker, but also by other unit tests that might not need the mock service worker.
Then the next step is to actually set up mock service worker and to set up some handlers. So for setting up the mock service worker, we need to actually create a setup file. So in the white disk config, we can configure some setup files. So you will provide the path to the file that is setting up our testing environment. Then here in test setup, I have this index.js file that has my global testing setup. So that's the setup that is used in all the tests that I'm writing. I also have some other stuff here, but the important part is here, the mock service worker setup file, where I connect mock service worker with YDist. So before all, after all, and after each test, we're starting the server, we're closing the server, and then we're resetting the handles after each test.
3. Mock Service Worker for Efficient Testing
The core part of setting up mock service worker is importing it and providing handlers. Handlers connect the API with specific methods, such as GET requests, and return a JSON mock response. This setup allows us to write unit tests for specific pages or endpoints. With the server and handlers in place, we can begin writing our first unit test.
And then the actual server that we're creating. So that's the core part. We're importing from mock service worker and we're providing it with some handlers. The handlers are the core part of setting up mock service worker. And what the handlers do is that they connect a REST API, or you can also do a GraphQL API with a specific method. So you say, for example, I have a GET, a REST GET request that cause the endpoint API store block, whatever. And when that happens, I want to return the status 200 and then please return a JSON of this mock that we just created. So we're returning a mock response and we can use that then in our unit tests. We do the same thing for the more specific page of that magical beast. So here we're really connecting JSON example response with the endpoint and this connection will help us write then the unit tests. So you do not need much more than that, setting up the server and setting up the handlers and then we can already start flying and write our first unit test.
4. Writing Unit Tests for Asynchronous Components
To write a unit test for this page, we need to consider its components and asynchronous nature. Vue's suspense component helps with handling asynchronous behavior. We import the asynchronous component and wrap it in a suspense component for testing. To wait for the fetch to happen, we create a spy on window fetch. We then mount the component using the mount function from the view test utils. After waiting for the fetch to be called, we use the flush promises helper to ensure promises are resolved. Finally, we write the tests and access the h1 element.
So the next step here is really to write a unit test for this page. When writing a unit test, you have to think, well what does it do? It shows a headline and it has three cards that have some content. So the view setup of it is pretty simple. But the complexity here comes a little bit with the assynchronity. So we're testing an asynchronous component. And in Vue, we can do that by wrapping it in a suspense component. So this is a fairly new component in Vue 3 that can help us deal with asynchronous behavior.
All right. Let's look further into it. So we have that file in our Vue that's asynchronous, like here, for example. We have some html. And the important part is that we have a fetch, and we have to wait for the response of the fetch. If we look into that component, we can see here we're importing the component. And then we're wrapping it in an asynchronous component just so we can test it, because it is an asynchronous component. And then we can write the actual test.
So in the actual test, what we do is, since it's asynchronous, we wait for the fetch to happen. How you can do that together with mock service worker is by creating a spy. So we're creating a spy on window fetch, and we will call that getSpy. You can do that also with Axios. So you could create a spy that works with Axios that does the same kind of spying. It really depends on which way you're using to talk to your API and get your responses. The next step is to actually mount the component. So we get this mount function from the view test utils. And then comes the asynchronous part. We wait for this get, this fetch function to have been called one time. And then we use a helper that's called flush promises. It's a helper for the view test utils to help us make sure the promises are solved. And then finally, we write the tests. So we get the wrapper for the test. We get the h1 element.
5. Connected Unit Test with Mock Service Worker
We get the text of the h1 element and ensure it matches 'my magical beast'. We verify that there are exactly three elements with the VA card class, simulating three cards in the unit test. The global test setup automatically fetches the JSON response when the endpoint is called, allowing the component to function as it would in a real environment. The test cases include checking if the heading is shown, if there are three cards, and if the second card displays the expected content. MockServiceWorker can simulate error responses or longer delays by creating new handlers. Error handlers return a 403 status and an error message, mimicking a scenario where a user calls an unauthorized endpoint.
We get the text of that. And we said, well, the text of my h1 element should be my magical beast. And then we find all the elements that have the VA card class. And we say, well, there should be three elements that have the VA card class. So that are three classes. So it makes sure there's exactly three cards in this unit test. And that's basically already our connected unit test with mock service worker. And it's pretty cool because we don't have to write this really crazy extensive mocks. But with this global test setup, when our endpoint is called here with the window fetch, it will automatically fetch this JSON we have here. And the component works as it would work also in the real environment. And then when we write the test, we can make sure that the setup of that component is actually like it should be.
So let's look at the test we wrote here. One second, I just run the file. So here, we have this before each. So it mounts the component before each test so that we do not have to rewrite this in every test. And then we have the different test cases. The first one says, well, the heading should be shown. There should be three cards. Then we have also another test case where we say, well, the second card should always show an if-not element that tests actually for that content we have there to make sure the content is shown in the way that we want to show it. All right, and yeah, that's already connected.
And now comes the interesting part because MockServiceWorker, it can not only simulate these positive responses, but it can also simulate error responses or longer delays. And we can do that by just creating new handlers that actually return something else. So inside of our mocks, I created a file that's called error handlers. And error handlers looks really similar to the handlers that we had before. Again, it's using the rest method from MockServiceWorker. We make a GET request to the same endpoint, but instead of returning a context status 200, we're now returning a context status 403. And then the endpoint will return an error message with not allowed. And that basically would mock a behavior where you'll say a user is calling an endpoint that is not allowed to call that input. For example, a user is calling an admin endpoint, but the user might not be an admin. To show you what I mean in the browser, I can show you that here.
6. Testing Error Responses and Edge Cases
So here, we can block a URL and see the error. I adapted my component to handle error responses. To test this behavior, import the server and error handlers, create a test suit for failing requests, and check if the HTML contains the error response. Testing with failing API responses ensures coverage for potential failures. Mock Service Worker makes testing easier and more powerful, allowing for testing edge cases like delayed APIs or custom error messages. MOCs can be used in unit tests without the MOC service worker.
So here, if we look at the network again, let's reload that. And then we can block this. We can block this URL. And now if we reload, it cannot load, because this URL is blocked. The browser is not allowing us to. And then we see this error here.
So I adapted my component to be actually able to handle this kind of error response. So inside of my home view, it's gotten a bit more complicated where I checked the response. I checked the response status. And if it's not at 200, I'm throwing an error. And this error is then shown inside of my page. And now I can write the test for it.
So to test this behavior with errors, what we need to do is we need to import our server that we're using that is active. And we need to import our error handlers that have the actual error handling responses. And then I can write a new test suit for the failing requests. And I can say, please use the server with the error handlers instead of the other regular handlers to see how my component behaves with a failing API response. Then I wait for that to be loaded and finished. And then I write the test case where I say, my HTML should contain this error response that is returned from the error handler. So the text that the API's returning should be shown in my HTML. And if we look at these error handlers, this is the message that is returned here. And then you're testing the same component, the same asynchronous component, with actual failing API responses, which makes it even more clear and make sure that we're also covering cases where the API might be failing. So this is really cool. And this is something that's rather hard to do when you have to write it yourself and you don't have the mock service worker. So you have to do a lot more without the mock service worker to get this kind of testing behavior. So it's really, really powerful. You can also test other edge cases. So APIs have unusual behavior, and you can test test cases, for example, where the API is delayed for two seconds. Or you can test cases where you have some custom error message or some custom response because some custom case happened, like a user calling an endpoint that he should not be calling. Of course MOC service worker is not the only way to test. You can use these MOCs that we created also in unit tests that are not using the MOC service worker.
7. Testing Components without API Calls
For components that don't call an API, the testing library is recommended. It's a wrapper around the view test utilities, providing better accessibility testing. It ensures the right elements are on the screen and tests user interaction. Let's look at an example of testing a component with a click behavior. The component is a simple view component that receives a prop called Block. The test imports necessary functions and components, sets up the mock data, renders the component, and executes test cases.
So for components that are not maybe calling an API, they're just regular components that are not asynchronous. For the smaller units, I can really recommend using the testing library. It's a really nice wrapper around the view test utilities that can test even better for accessibility. It can test if the right elements are on the screen. It can test for user interaction. So it's really good in making these click behaviors or input field behaviors to make sure your components are behaving like they should for user interaction.
And I will show you a quick case of our example. So let's disable the blocking here. So if we go to our element here, it's a simple component that receives a prop. And we now want to test those component and maybe test this click behavior that we have here on the buttons, or that we have a button. I will show you the actual test.
So this is our component. So it is a simple view component with a lot of HTML. And it basically only receives a prop called Block. That's very common in applications that use Storyblock. And then everything that's shown here is based on top of this block prop it's receiving. And then here we can go into the test for this component. And you see here we are importing the render screen and fire event functions from the testing library view. So that's kind of the core functions you would need when you test with this library. I'm also importing the config because I wanted it to receive some of the global configuration I already had configured. And then I import the component.
Here I don't need this async wrapper because it's not an asynchronous component that's waiting for some API response. It's just a simple component. And I'm also importing this mock that I created in the early beginning where I just copied the response I got from the server. I then create the actual prop that is passed to the component. So what's passed to the component is inside of that whole mock. So I get the specific part of the mock that I need to pass it to the component. Then I render the actual component. I pass the content, the mock, to that component. And then I can have the different test cases.
8. Testing Component Interactions and API Integration
In addition to testing specific attributes, this library allows for testing interactions and ensuring component behavior. By using the same mocks as the mock service worker, we can define the interaction point between the API and the application in one place. This ensures that components are working with the API's data and helps prevent application breakages. For more resources, check out the mockserviceworker documentation, the testing library for testing components and interactions, and Vue.js guides on asynchronous behavior and suspense. You can also use Storyblock to create your APIs. Try it out in your Vue 3 application!
So here I'm testing that the name of the magical piece, the description, and the image are there. I can also test. And that's what's cool about this library is that I can test by alt text. I can test by placeholder. And this can be really useful for making your application more accessible because we do not only tests for specific tests, but for actual HTML attributes.
And then finally, I can also test some interaction. So here I'm saying I want to have exactly one button with the text. And then when I click on that button, I want to be sure that this component emitted the specific event. So this basically tests this behavior that this component is emitting an event when we click on it. And now if someone would go into this component and remove this button, then our unit test would fail. And we would see, well, something went wrong because it's not fulfilling this button behavior anymore. The same thing would happen if this person would change this button to a div. The unit test would be failing because we've been really specific in saying, well, there should be a button with that name that should emit exactly this event. And this helps us make sure that our application is not breaking.
And as you see here, it's not using a mock service worker. It's not asynchronous, but it's using the same mocks that we're also using together with the mock service worker. And that's really helpful because we have one place where we define the interaction point between the API and the application. So if all the tests are using the same kind of mocks and if my API is changing, I also have to change the mocks. But I can make sure that my components and my components when I'm testing them are actually working with all the things that are coming from my API.
All right. That's basically it. I have some more resources. So mockserviceworker has a really nice documentation. The testing library is really great for testing simple components and testing interactions. Vue.js has also a lot of great guides on how to work with asynchronous behavior, how to work with suspense, and also how to test asynchronous behavior because it's not actually that easy. And yeah, you can also use a story block to create your APIs. And yeah, that was really fun to actually do and learn and set up. And I hope you use this repository to try it out in your own Vue 3 application. Thank you very much for listening.
Comments