Check out a free preview of the full Complete Intro to React, v3 (feat. Redux, Router & Flow) course:
The "Testing Thunk" Lesson is part of the full, Complete Intro to React, v3 (feat. Redux, Router & Flow) course featured in this preview video. Here's what you'd learn in this lesson:

Since handling asynchronous behavior and mock out AJAX requests to test thunks is difficult, Brian introduces moxios, which is a helper for axios in testing. - https://github.com/btholt/complete-intro-to-react/tree/v3-26

Get Unlimited Access Now

Transcript from the "Testing Thunk" Lesson

[00:00:00]
>> Brian Holt: So the last thing that we're gonna want to test is our thunk. That's a little bit more complicated cuz now we're gonna have to start dealing with asynchronous code, it's not so easy, but it's not horrible, which is the good news. So the first thing we're gonna import up here, and this is actually one of the reasons I chose Axios for this course, is actually has its own mocking library called maxious.

[00:00:27]
>> Brian Holt: Let me push a branch real quick. So I'm gonna yarn add dot, yeah. Why am I doing this, git add. Get checkout -b v3-25, git comit -m 25, git push origin v3-25. Okay, so v3-25 is out there. I guess I pushed it with moxios in there, so you'll have that lint in there, but that's okay.

[00:01:06] So now we're going to go do some testing with moxios. So the first one we're going to do is we're going to pull this ad API data out. And we're just gonna say this is strangerThings.
>> Brian Holt: And we're gonna say const strangerThings equals block, and then we can reuse in this next test here.

[00:01:36]
>> Brian Holt: So we're gonna test getAPIDetails.
>> Brian Holt: getAPIDetails.
>> Brian Holt: And up here we're gonna import getAPIDetails,
>> Brian Holt: So Moxios has this cool ability to do moxios.withMock,
>> Brian Holt: And then inside of here, if I ever call axios, it's just going to substitute the mock version instead of actually trying to make a request, right?

[00:02:19] So anything inside of that body will happen. The first thing we're going to do is const dispatchMock= jest.fn, this is called a spy function. Ultimately, what we're concerned about is given inputs, right? If I request something, I'm going to expect it to, for the dispatch to be called with the correct functionality.

[00:02:56] So if I request Stranger Things I'm going to expect it to eventually dispatch an action to update that in Redux, right? That's what I'm testing here. So that's why I'm gonna use this dispatchMock function. I'm going to pass this in as the dispatch function and test later if it was called with the correct parameters.

[00:03:15] That's that's what we're going for here.
>> Brian Holt: That make sense?
>> Brian Holt: So another thing about this is this test is going to be asynchronous, right? It's not going to complete synchronously, right? Like this one, there's no asynchronous component to it, whereas with Moxios and with API request, it's asynchronous in nature.

[00:03:40] So, we have to be worried about that we don't want the test to complete before that we've actually done the, finish the testing, right? So what you can do here is you can say (done: Function), this is an ability for Jest right. This done function, this is when you call it and say, I'm done, right.

[00:03:57] So, as soon as you call done your test case is finished and so, if everything hasn't wrapped up it's gonna fail your test. It also knows that don't finish the test case until I call this, okay. So what I'm gonna do here is withMock, how do I do this outside, it doesn't really matter, but whatever.

[00:04:19] withMock I'm going to call getAPIDetails, and I'm going to pass in strangerThings.imdbID. Right, what does this return to me? It's a thunk, right? So if I call getAPIDetails(strangerThings imdb), this is going to return to me another function. Typically Redux calls this function for us, but we're not involving Redux in this process.

[00:04:47] We're testing just this thunk. So we actually have to call the function given back. So that's why we're gonna put a second set of parentheses here cuz we're gonna call the function that's given back. If this is more clear to you you're welcome to say like const thunk = that and then call thunk right after it.

[00:05:06] If you want to be more exclusive that way, that's totally fine as well.
>> Brian Holt: What we're going to do here, is we're going to call it, and what is that function called with? So if we go look at our action creators.js, if you look right here, what is it called with?

[00:05:22] It's called with Dispatch, this is injected by Redux. Right, so if we go back to actioncreators.spec.js, we want to call it with our dispatchMock. And so now that's going to be provided to our function and we're going to wait to see if that's called with the correct parameters.

[00:05:41] That's what we're actually testing here.
>> Brian Holt: So what we're gonna say is hey moxios, just chill out for a second. So you're gonna say moxios.wait,
>> Brian Holt: You can give that a callback function, and then we're gonna say, const request =noxios.requests.mostRecent. So the first thing we're gonna check to see is that was it called with the right URL?

[00:06:14] Did our thunk reach out to the correct API, right, and we can inspect that request here, given back to us by noxios. So we're gonna say request.respondwith,
>> Brian Holt: Status 200, and the response is going to be strangerThings. So yeah that's what we're doing here, in particular, it is, it's going to make that request and you'll say hey respond to this request with a 200 and then like our next case test, excuse me.

[00:06:56] Test case could be text, what happen is that this is 404 and this is like error, right? We can test that as well. Not gonna test that right now but that how you do that as well. This is gonna give you a promise, so I'm gonna say .then and in here we can test what actually gonna happen.

[00:07:19] So the first thing we gonna expect is we gonna to expect ( request.url ),
>> Brian Holt: toEqual( http://localhost:3000/$ ) Stranger things.indbid right? So that's the url it should be calling right? The next thing is we're going to say expect dispatchMock,
>> Brian Holt: toBeCalledWith. This is a function of the spy function that we're using here.

[00:08:05] It's very similar to sign on, if you've ever used that one, and we're going to expect it to be called with add API data, right. That's ultimately that the action that we're looking for, so it's gonna be add API data with stranger things.
>> Brian Holt: And then down here we're gonna have to call done, right?.

[00:08:30] Otherwise, it doesn't know that it's finished. So done().
>> Brian Holt: So a big key here is we're testing kind of integration between thunk and then eventually getting to Dispatch and the addAPIData. We need to test addAPIData before we test the thunk, right? That actually really key here. Sp that why addAPIData is up here, right?

[00:08:56] We need to be sure this ironclad before we test this one, because if this fails, we want this test case to fail first. We want this one to say, hey, this is messed up, and all these tests that are dependent on this working need to like, this needs to be tested first before we get to here.

[00:09:16] So the fact that we've tested this first means that I feel okay down here testing the integration between the two, okay?
>> Brian Holt: So that's the two things we're testing here. We're testing that the correct API is being called, right? And then we're testing that the correct action is being dispatched.

[00:09:38]
>> Brian Holt: So now if we come here and say, yarn test, hopefully we should pass.
>> Brian Holt: So the hallmark of any good test is to go make sure that actually fails when you anticipate it should fail, so that's always what I do. I'm always suspicious of things that pass the first time.

[00:10:03] So let's go into action creators and make sure that it fails. So the first thing let's say like, I'm gonna update the URL to 9 thousand, right? I should expect that URL to fail. So now it's saying, hey I expected this to be 3 thousand and I got back 9 thousand, and that's a problem.

[00:10:23] So, that test is good.
>> Brian Holt: Yeah, so that's good. So that failed the way I wanted it to. And let's say that this dispatch is, I don't know, null for now.
>> Brian Holt: Let's go make sure that that fails as well.
>> Brian Holt: And that fails as well because it's saying, hey, I expected to get this back, and it didn't get that, so that sucks.

[00:11:03] So, cool, that fails as well when we anticipated it should fail.
>> Brian Holt: Gotta make sure that I de-broked everything and it all works, okay.
>> Brian Holt: Any questions about this?
>> Brian Holt: To be totally honest with you, going back to our spec here,
>> Brian Holt: Like mocking API responses and testing that.

[00:11:39] I mean, obviously more involved than any of the other tests we wrote, but I would say this is pretty good for testing asynchronous code, right? It's pretty nested, right, where how many layers deep, but at the same time, this reads pretty well to me. So the fact that we can test that in one small chunk, and now we have confidence that get a APID tells us not going to regress over time unless we anticipate changing it in some direction.

[00:12:08] So this is a good test case. I'm pretty happy with this. Again, this is one of the big strength about Redux, as we can isolate this little tiny pieces and then just really test them super well. And, then you can kind of have bigger and bigger test case that cover more and more of the stack, to get more of like an integrations that have just unit style test.

[00:12:27] So I'm a fan.