Mocking APIs in Tests with Nock
HTML-код
- Опубликовано: 25 авг 2024
- In this video we talk about how to handle APIs when testing components-specifically in React Native, but the principles apply to any component-based UI. We talk about when you would and wouldn't want to mock APIs. We walk through using nock and talk about the mentality of testing the contract between frontend and backend. Then we compare nock to two alternate approaches to mocking APIs: Mock Service Worker and Jest module mocks.
Hey! Thank you so much for putting this comparison. I'd like to elaborate on some points from the video but in no way I'm going to defend MSW or anything :) Just sharing my insights, hope it's helpful.
Regarding matching a request based on its body: although it's not in the request matching section, you can absolutely do it. The philosophy behind MSW is that you write handlers that represent your server logic. Just as you declare server handlers, you start from a path and you have a resolver function to deal with other details. MSW works in the same way: you capture your request based on its method and path, and in the resolver, you can provide *additional* matching details:
rest.post('/todos', async (req, res, ctx) => {
const requestJson = await req.json()
if (requestJson.something === 'otherThing') {
// mock the response
}
// pass it through
})
I understand it may feel verbose but that's another thing to mention: MSW is really meant as a description of your network layer. I can't say I like how mocking is a part of your testing tooling as opposed to being its own thing. Because, as it turns out if it's its own thing it can be reused between tools, shared across environments, and stand for a much broader term than simple request stub. Also, I often encourage people to create their own matchers to suit their needs. I've tested numerous apps of different sizes and never did I need to mock a request based on what body it was sending. I don't imply that's not a thing but rather saying that not everyone needs this exact way of matching, and so implementing it yourself is both straightforward and transparent.
Okay, request assertions. You provide a valuable point that referring to it as "implementation detail testing" may feel like shaming. I may need to phrase that statement better because that's not the intention behind it. I intend to guard people against writing inefficient tests, and you won't believe how often people test a request URL, or its body when they can omit that assertion and keep the very same value in their test (if not make it better!). It's not about shaming but rather guiding people to do intention-driven testing. And the intention behind any request is to get the response and handle that response in some way. In 99% of the cases, the response is handled in the UI in some way. So asserting *that* makes for a much better best test, imo. Also, if you dispatch an invalid request, your application's logic won't handle it correctly, there won't be the expected response, and it won't be reflected in the UI. Response-driven assertions stand solid and there are very few examples when they don't. So this felt like a great default to encourage people to do, and thus the rhetorics around request assertions may feel a bit defensive. By the way, you can still assert requests with MSW by catching a request and asserting its properties. It's fully supported, just not encouraged ;)
Also, I'm really glad you're using the tool that suits you. Nock is a fantastic one and I've learned a lot by reading its source. I dislike how library authors just force their creations on everyone. In the end, we need libraries to achieve our goals, and if a certain library already does so efficiently, I see no reason to change that. Once again, thank you for the video!
Thanks for sharing all this info! It was helpful to me and I hope it's helpful to any other viewers too.
> MSW is really meant as a description of your network layer. I can't say I like how mocking is a part of your testing tooling as opposed to being its own thing. Because, as it turns out if it's its own thing it can be reused between tools, shared across environments, and stand for a much broader term than simple request stub.
That totally makes sense. I think there are some tradeoffs there in terms of mocking *specifically* for one test case vs setting up a general mocking setup. You can see a similar tradeoff of the "factories vs fixtures" question when it comes to test data. So either way could be useful for different cases.
Incidentally, on my current client project we are looking at setting up Mock Service Worker within Storybook 👍
> I intend to guard people against writing inefficient tests, and you won't believe how often people test a request URL, or its body when they can omit that assertion
Yes, totally. The explanation I heard that helped me understand all this is from Sandi Metz in this talk: ruclips.net/video/URSWYvyc42M/видео.html - She distinguishes between "queries" (just retrieve data) and "commands" (modify data) and says that asserting on outgoing queries is an over-specification: it adds costs but not benefits. Because if you're asserting on the results of the query's return value already, you get no additional benefit. I recommend that talk to anyone wanting to learn more about mocking in testing in general
Hi, what is your configuration? I can't get nock to work in react native
Thanks Josh, I found this really useful!
I especially liked your response to 'implementation details testing'. How can I test a backend-agnostic frontend without specifically testing what data it's sending to the server?! Blanket statements like the one in msw and RTL docs aren't always very helpful
Thanks a lot for these amazing streams! One question, when we use nock, we are using localhost:3000, does it matter? What if I don't have my backend running on localhost:3000?
Good question! When you call nock() you want to specify whatever hostname and port your app is actually requesting; that will allow nock to intercept it. Docs: github.com/nock/nock#specifying-hostname