Is your feature request related to a problem? Please describe.
When using msw in a test environment, I'd like to be able to retrieve history of requests / responses for a certain handler. Do they get stored anywhere?
When using axios, there's a way to do it if you use axios-mock-adapter, that provides history per method.
Without mocking axios, I can't find a way to retrieve the last request made so I can verify that a request with certain params had been made.
Given
export const findPets = (petResponse?: Pet[]) =>
rest.get(`${endpoint}/pets`, (req, res, ctx) => {
const PetResponse = petResponse ?? PetStub.buildList(10)
return res(ctx.delay(0), ctx.status(200), ctx.json(PetResponse))
})
PetsList.test
it('should return n results when limit is set', async () => {
const FindPets = findPets()
server.use(FindPets)
render(<PetsList />) // Component makes a request with a query param {limit: 2}
await waitFor(() => screen.getByText(/List of pets/i))
/* Here I'd like to be able to retrieve GET request and assess it's params so I could validate that component's sending proper params, a la
expect(FindPets.history[0]).toEqual(expect.objectContaining({ limit: 2})
*/
})
Additional context
| Name | Version |
| ---- | ------- |
| msw | 0.20.5 |
| node | 13 |
| axios| 0.19.2 |
Hey, @ilyaulyanov. Thanks for reaching out.
I'm afraid accessing such list and performing assertions on it would be implementation details testing. Instead of checking internals of MSW and looking up if some handler was called with some parameters, think of it this way: if a handler is called with _invalid_ parameters, your React component's output will be invalid. Assert what your component renders, not the network layer.
Take a look at the similar question that's been raised recently. I go into details and provide usage examples that lean your test towards testing what matters.
I can see such API being useful only for development purposes. It brings too much temptation to test if something was called, or was called with the right parameters, which is something MSW actively discourages. Test how your component reacts to the response, emulate unsuccessful and error responses, provide conditional mocked responses based on the request parameters in your response resolvers鈥攖his will give you much more confidence in your tests.
Hope this helps.
@kettanaito thanks for an elaborate and quick response. I agree that having the ability to see that would be tempting, and lead to implementation testing and all problems associated with it.
Having to write a conditional mocked response seems as the way to go here for me. Even though it has a risk of introducing false positive / negative results due to an incorrect implementation, it's still way more "real" than a mocked API module. I'm currently experimenting with msw and how it can help remove the "fake" layer of mocked modules and requests, when testing an API service and, maybe, use code generation to create handlers based on API schema.
It certainly feels like with msw, it's as close to 100% real as ever. Even though, the last bit of having to re-implement some parts of business logic (e.g. conditional response based on a value of a param) takes away from "realness" a bit.
If you see conditional responses as a solution to mine & similar issue, maybe it's worth adding it to the Recipes section? I can help if needed. The rest of the runbook, including recipes, was really helpful when setting it up for an existing app.
@ilyaulyanov consider reusing the condition logic from the rest of your app. Since mocks execute in the same context, you can call the same validation functions in the mocks, for example, that you do in the actual application's implementations. This is a solid way of ensuring you don't get false positive/negative results in your tests.
Even though, the last bit of having to re-implement some parts of business logic (e.g. conditional response based on a value of a param) takes away from "realness" a bit.
It may feel like repetition, but in reality it's depicting the behaviors that make your request valid. Not all business logic should be copied to response resolvers. The one that does helps you to reason about API changes over time, acting like a snapshot of an API contract once established. This is one of the arguments against any kind of sync between the actual server and mocks: it would destroy the reproducibility of a mock.
Thank you for the suggestion, I think it's a good idea to put these recommendations into a recipe. I've added the Request assertions recipe, please take a look.
@kettanaito
I've added the Request assertions recipe, please take a look.
Looks good to me!
hey @kettanaito - thanks for this explanation here, but I seem to have a caveat / related question for you...
How could one go about testing different UI state based on an Apollo query that utilizes a pollInterval?
https://www.apollographql.com/docs/react/data/queries/#options
I.e. first request responds with this result, second request delays, then responds...etc...
Any ideas?
Hey, @priley86. Thanks for reaching out.
The pollInterval on useQuery hook is a client-side option. It controls how your client polls for data. From the MSW perspective there's no polling, but a plain response to a request.
const GET_USER = gql`
query GetUser {
user: {
firstName
}
}
`
function Example() {
const { data } = useQuery(GET_USER, { pollInterval: 1000 }) // request each second
// handle loading and error
return <p>{data.firstName}</p>
}
// mocks.js
import { setupWorker, graphql } from 'msw'
const worker = setupWorker(
graphql.query('GetUser', (req, res, ctx) => res(ctx.data({ user: { firstName: 'John' } }))
)
worker.start()
This way your useQuery hook will request data each 1000ms, while MSW request handler will respond to each of those requests as usual.
Does this answer your question? Feel free to clarify in case I got it wrong.
I think this example helps and helps me think differently about polling and testing w/ msw. Initially I was thinking that some kind of initial response could occur, and then a subsequent one after a delay with a different response (say to test the loading and error states above) using the same resolver. The client would be making the same query and just initiating a poll. However another potential way of testing these different loading/error states having the same query vars after a poll is just referencing different msw resolvers in your test. Thoughts?
i.e., no request history would be needed...
Initially I was thinking that some kind of initial response could occur, and then a subsequent one after a delay with a different response
However, this is not how polling works. Polling is entirely client-side technique that provides data synchronization via repetitive request in a defined time interval. From the server's perspective polling doesn't exist, as the server receives a request and responds to it as usual. In a traditional HTTP communication server cannot send a response without a preceding request first. You may be confusing polling with real-time subscriptions (i.e. WebSocket), which are not yet supported by MSW (see #156).
However another potential way of testing these different loading/error states having the same query vars after a poll is just referencing different msw resolvers in your test.
Sorry, I'm not sure I follow you on this one. Please, could you share some code (pseudo-code is fine) of how you see this working?
Most helpful comment
@ilyaulyanov consider reusing the condition logic from the rest of your app. Since mocks execute in the same context, you can call the same validation functions in the mocks, for example, that you do in the actual application's implementations. This is a solid way of ensuring you don't get false positive/negative results in your tests.
It may feel like repetition, but in reality it's depicting the behaviors that make your request valid. Not all business logic should be copied to response resolvers. The one that does helps you to reason about API changes over time, acting like a snapshot of an API contract once established. This is one of the arguments against any kind of sync between the actual server and mocks: it would destroy the reproducibility of a mock.
Thank you for the suggestion, I think it's a good idea to put these recommendations into a recipe. I've added the Request assertions recipe, please take a look.