Both Jest and React Testing Library are very powerful tools: they let you simulate just about anything you want.
Most of the content for the Shiny app comes from data that you get from an API (as we will be getting our content from a CMS). API calls are no exception to the rule. The data can be simulated in tests known as mocks. This means you can also test other components. 🚀
Test Your Components That Make API Calls
Install msw
to Mock Your API Calls
You will need to do some configuration to be able to simulate API calls. Don't worry if you're not a fan of config, it won’t take long!
React Testing Library recommends using an external library: MSW (Mock Service Worker) to make mocks. It's hosted in GitHub. Start by installing the library:
yarn add msw --dev
The msw
library will intercept the API calls that your components make during tests and mocking out what would have been returned without your app ever knowing what’s going on. Believe me, it’s 🔥.
Create Your Mocks
To do this, you need to configure a “server” that will handle the interception of API calls in each of your test files. Start by testing a component on the Freelancers/index.jsx
page. Then create a file in /pages/Freelancers
and call it index.test.js
.
You’re going to need rest
from msw
, so do the following:
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { render, waitFor, screen } from '@testing-library/react'
import Freelancers from './'
const server = setupServer(
// Specify the url that we want to "intercept"
rest.get('http://localhost:8000/freelances', (req, res, ctx) => {
// Here we can pass the mocked data into what is returned in json
return res(ctx.json({}))
})
)
// Activate the API mock before the tests from server
beforeAll(() => server.listen())
// Reset anything we might have added in terms of duration for our tests before each test
afterEach(() => server.resetHandlers())
// Close the API mock once tests are over
afterAll(() => server.close())
And that’s all the configuration you need! ✨
Where’s the data that we return?!
Well spotted! It's not in there yet. You need a format that matches what the API returns. If you do a console.log
of whathttp://localhost:8000/freelances
returns, you’ll see that it’s a list of objects. So create a list of objects for your mock:
const freelancersMockedData = [
{
name: 'Harry Potter',
job: 'Frontend wizard',
picture: '',
},
{
name: 'Hermione Granger',
job: 'Fullstack witch',
picture: '',
},
]
It returns the following in your mock:
const server = setupServer(
// Specify the url that we want to "intercept"
rest.get('http://localhost:8000/freelances', (req, res, ctx) => {
// Here we can pass the mocked data into what is returned in json
return res(ctx.json({ freelancersList: freelancersMockedData }))
})
)
You’re all set, so now it’s time to use it! 🔥
Use Your Mocks
Now that the configuration is complete, let's use it to test pages/Freelancers/index.jsx
.
But before testing, consider your testing strategy. The component displays a loader while it makes the API request, then displays the data in the Cards
components. So to start with, you can check that:
1- The loader displays correctly during the call.
2- The first card
correctly displays the elements fetched in the call with the first element.
Start with the first stage. You know that isLoading
is set to true
, so you can check that the Loader
appears as it should.
How? The Loader
is a simple styled div
and doesn’t contain any text!
The time has come to use data-testid
(remember from the last chapter?). To use it, specify this in Freelancers/index.jsx
:
<Loader theme={theme} data-testid="loader" />
```
And in our test, we get it with :
```
test('Should render without crash', async () => {
render(
<ThemeProvider>
<Freelancers />
</ThemeProvider>
)
expect(screen.getByTestId('loader')).toBeTruthy()
})
Your test is running. It works. ✅
How do I know that my test isn’t always right? That it'll just work whatever happens?
Try changing the id
to check that it breaks properly:
expect(screen.getByTestId('thisIdDoesNotMatchAnything')).toBeTruthy()
And the test fails.
To test with your data, you’ll need waitFor
, which you already imported from ' @testing-library/react
' . This method manages asynchronous code, like with an API call, for example. 😉 Don’t forget to also add an async
before the test callback.
To check that the code correctly displays the names Harry Potter and Hermione Granger, you have:
it('Should display freelancers names', async () => {
render(
<ThemeProvider>
<Freelancers />
</ThemeProvider>
)
expect(screen.getByTestId('loader')).toBeTruthy()
await waitFor(() => {
expect(screen.getByText('Harry Potter')).toBeTruthy()
expect(screen.getByText('Hermione Granger')).toBeTruthy()
})
})
Once again, you can change the text, like this:
expect(screen.getByText('Harry PotOfButter')).toBeTruthy()
And the test fails! 🔥
You can see another example of mocks and a testing strategies in the screencast below 👇:
Customize Your render
So far, you've used your Theme
directly in your tests. That’s not very clean, especially if you’re testing other components like Home
when you’ll also have to wrap it in the router.
But that’s no problem because the render function from React Testing Library can take a wrapper as a parameter.
Declare a new React component ,Wrapper
, in the previous test.
function Wrapper({ children }) {
return <ThemeProvider>{children}</ThemeProvider>
}
And reuse it by passing it as a parameter of your render
:
render(<Freelancers />, { wrapper: Wrapper })
Ta-da! 🎉
You can even turn this into a tool that you reuse in all your tests.
To do this, create a /test
folder in /utils
, and put an index.js
file where you will put the tool.
Which gives you:
import { render as rtlRender } from '@testing-library/react'
import { ThemeProvider } from '../../utils/context'
function Wrapper({ children }) {
return <ThemeProvider>{children}</ThemeProvider>
}
export function render(ui) {
rtlRender(ui, { wrapper: Wrapper })
}
And now all you need to do is import your new render
in your /pages/Freelancers/index.test.js
, and delete render from imports with ' @testing-library/react
' .
It works perfectly! 🎉
While we’re here, let's deal with the Router
and SurveyProvider
. If you want to explore alternative options (and if you need to run tests that have access to your history
), look at the React Testing Library documentation.
In our case, we just want the tests to work even when there’s a Link
in the components. To do so, transform your file to add the Router
and SurveyProvider
:
import { render as rtlRender } from '@testing-library/react'
import { ThemeProvider, SurveyProvider } from '../../utils/context'
import { MemoryRouter } from 'react-router-dom'
function Wrapper({ children }) {
return (
<MemoryRouter>
<ThemeProvider>
<SurveyProvider>{children}</SurveyProvider>
</ThemeProvider>
</MemoryRouter>
)
}
export function render(ui) {
rtlRender(ui, { wrapper: Wrapper })
}
The test works! ✨
Discover Other Types of Tests
You’ve learned how to create unit tests with Jest and test your components with React Testing Library. Then you tested your interactions and mocked API calls. But remember that testing is a huge topic. There are many different tools and approaches.
In the screencast below, you’ll see a demo of another approach that you haven’t seen. 👇
As mentioned, end-to-end testing is another powerful tool. To learn more, use the Cypress Testing Library.
Like with everything else in JavaScript, test tools are constantly evolving. So always watch for new ones and then read their documentation.
Give It a Go!
It’s time to put what you’ve learned in this part into practice. You’re going to create tests for Results/index.jsx
. You’ll also have to mock the data, like you’ve seen in this chapter.
As usual, the code for getting started is on branch P3C3-begin with the solution on P3C3-solution.
Let’s Recap!
The
msw
library helps with mocking API calls from tests, letting you configure aserver
that returns the desired mocked data.React Testing Library contains tools such as
waitFor
, allowing you to test your components after API calls.It is possible to customize the
render
to include theRouter
and theProviders
from context.Other types of tests exist, such as end-to-end testing.
Well done! You’ve finished the part of this course on React that deals with testing. 💪 It’s now time to test your knowledge with a quiz. Good luck, and see you soon for the fourth and final part of this course!