In this lesson we go after the elusive goal of 100% test coverage by testing a few additional cases: error responses, empty error messages and the pending state that should exist between checkout being clicked and the API returning data.
The first two require special handling in our mock API:
if (items.evilItem > 0) throw new Error();
if (items.badItem > 0) return { success: false };
In some ways these are arbitrary examples that may not exist in your other applications, but I wanted to demonstrate the fact that there are a lot of different ways an API might decide to communicate errors.
In general with AJAX calls a non-200 status response often gets thrown as an error and treated as an error case, but some APIs (such as graphql) include errors as part of a normal 200
response and need special handling. These lessons are meant to demonstrate what that might look like in your mocks.
Now it's useful to reiterate here that our simple API interface makes writing these mocks very easy, but in many cases something like Mock Service Worker is a more appropriate way to mock out an API.
For our "pending" test, it's interesting what we lose when we stop focusing on actions fired (like in our previous 2 lessons) and instead focus purely on the resulting state. It makes looking at the intermediary state a bit more difficult. Here we work around that by calling store.dispatch(thunk)
and saving the results of that to an action
variable, which we await
after doing an initial assertion. That await
triggers our mock API to respond, so we can use the space between dispatch
and await
to peak into the pending state.
Jamund Ferguson: ...open up cartslice.ts and scroll down to the checkout cart with full Redux store describe block. Here we'll add three additional tests. It.todo should handle an error response, It.todo should handle an empty error message, and It.todo should be pending before checking out.
If we quickly go to our code coverage, you can see here the case where the thunk is fulfilled, but success happens to be false. That's a time when we need to confirm the checkout state is set to error. There's also a case where a thunk is rejected. However, we don't receive an error message, and we just set that error message to empty string.
The final case that we'll be checking here will be to ensure that we set our checkout state to Loading before it finishes checking out.
Let's go ahead and kick off our test in watch mode. For our first test, let's make sure to use an async function. Inside of that, type const state = getStateWithItems. If you recall, up at the top in our API mock, we put in a special case where someone returns badItem > 0, we return false.
We'll use that here, getStateWithItems({ badItem: 7 }); const store = getStoreWithState, pass in the state. Now, we'll await store.dispatch(checkoutCart()). When that's done, we can expect(store.getState().cart).toEqual({ items: { badItem: 7 }, checkoutState should be "ERROR". The errorMessage will be an empty string. It passed as expected.
For the next one, we'll use the same setup. We'll also use an async function here. Copy the contents over. For this one, we need to update our mocks a little bit. Go to the top. Here, we're worried about throwing an error so we'll say evilItem > 0. Instead of returning false, we're going to throw a new error.
This one is not going to have any error message. We're just going to throw an error. This is not a good practice by any means, but we're trying to be comprehensive and testing all the possible edge cases. Now, we can say evilItem: 7. It should put us in a checkoutState of "ERROR", with an empty errorMessage. Indeed, that worked.
For our final test, checking it where pending, let's go ahead and copy over previous test. This time, we could have a good item instead of a bad one. What we're going to do differently here is save the action after we dispatch. This will dispatch it, but it's not going to complete the checkout process.
During this time, we can assert store.getState().cart.checkoutState is equal to "LOADING". Then, we can await our action that will allow our mock API to return. Then, we can expect getState().cart.checkoutState to equal "READY". We also add one before we fire off our action to confirm we're started in the Ready state. We go Ready, Loading, and then back to Ready again after our checkout completes.
Member comments are a way for members to communicate, interact, and ask questions about a lesson.
The instructor or someone from the community might respond to your question Here are a few basic guidelines to commenting on egghead.io
Be on-Topic
Comments are for discussing a lesson. If you're having a general issue with the website functionality, please contact us at support@egghead.io.
Avoid meta-discussion
Code Problems?
Should be accompanied by code! Codesandbox or Stackblitz provide a way to share code and discuss it in context
Details and Context
Vague question? Vague answer. Any details and context you can provide will lure more interesting answers!