I'm starting to use the msw (mock service worker) after watching this example of how to use it for testing API calls in React applications.
Is there any way that we can spy on the mock service worker?
For example:
import React from 'react'
import { render, act, await } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import SearchBox from '.'
const fakeServer = setupServer(
rest.get(
'https://api.flickr.com/services/rest/?method=flickr.photos.search',
(req, res, ctx) => res(ctx.status(200), ctx.json({ data: { photos: { photo: [] },},}))
)
)
beforeAll(() => {fakeServer.listen()})
afterEach(() => {fakeServer.resetHandlers()})
afterAll(() => fakeServer.close())
test('it calls Flickr REST request when submitting search term', async () => {
const { getByLabelText } = render(<SearchBox />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input,'Finding Wally')
await userEvent.click(submitButton)
})
await wait()
// TODO: assert that the fakeServer was called once and with the correct URL
})
The component to test looks like this:
import React, { useState } from 'react'
import axios from 'axios'
import './index.css'
function SearchBox({ setPhotos }) {
const [searchTerm, setSearchTerm] = useState('')
const handleTyping = (event) => {
event.preventDefault()
setSearchTerm(event.currentTarget.value)
}
const handleSubmit = async (event) => {
event.preventDefault()
try {
const restURL = `https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${
process.env.REACT_APP_API_KEY
}&per_page=10&format=json&nojsoncallback=1'&text=${encodeURIComponent(
searchTerm
)}`
const { data } = await axios.get(restURL)
const fetchedPhotos = data.photos.photo
setPhotos(fetchedPhotos)
} catch (error) {
console.error(error)
}
}
return (
<section style={styles.container}>
<form action="" method="" style={styles.form}>
<input
aria-label="Search Flickr"
style={styles.input}
value={searchTerm}
onChange={handleTyping}
/>
<button
aria-label="Submit search"
style={styles.button}
onClick={handleSubmit}
>
SEARCH
</button>
</form>
</section>
)
}
I have got a working test, but I feel it leans towards an implementation test since it uses a spy on the setPhotos
test('it calls Flickr REST request when submitting search term', async () => {
const fakeSetPhotos = jest.fn(() => {})
const { getByLabelText } = render(<SearchBox setPhotos={fakeSetPhotos} />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input, 'Finding Walley')
await userEvent.click(submitButton)
})
await wait()
expect(fakeSetPhotos).toHaveBeenCalledWith([1, 2, 3])
})
The devs at mswjs are really nice and helpful. They took their time to advice me on how to approach it.
The current working test I got is fine - just suggested an alternative to jest.fn()
- I do like the readability of the their suggestion:
test('...', async () => {
let photos
// Create an actual callback function
function setPhotos(data) {
// which does an action of propagating given data
// to the `photos` variable.
photos = data
}
// Pass that callback function as a value to the `setPhotos` prop
const { getByLabelText } = render(<SearchBox setPhotos={setPhotos} />)
// Perform actions:
// click buttons, submit forms
// Assert result
expect(photos).toEqual([1, 2, 3])
})
Another thing I wanted to test was that it actually calls a valid REST URL.
You can reflect an invalid query parameter in the response resolver. If the query parameter is missing/invalid your real server would not produce the expected data, right? So with MSW your "real server" is your response resolver. Check the presence or value of that query parameter and raise an error in case that parameter is invalid.
rest.get('https://api.flickr.com/services/rest/?method=flickr.photos.search', (req, res, ctx) => { const method = req.url.searchParams.get('method') if (!method) { // Consider a missing `method` query parameter as a bad request. return res(ctx.status(400)) } // Depending on your logic, you can also check if the value of the `method` // parameter equals to "flickr.photos.search". return res(ctx.json({ successful: 'response' })) })
Now, if your app misses the method query parameter in the request URL, it would get a 400 response, and shouldn't call the setPhotos callback in case of such unsuccessful response.
If you want to avoid mocking you could spy on axios.get
and assert that it was called correctly.
test('it calls Flickr REST request when submitting search term', async () => {
const getSpy = jest.spyOn(axios, 'get');
const { getByLabelText } = render(<SearchBox />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input,'Finding Wally')
await userEvent.click(submitButton)
})
await wait()
expect(getSpy).toHaveBeenCalledTimes(1)
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With