
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. 🚀
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 --devThe 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 🔥.
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! 🔥
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 👇:
renderSo 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! ✨
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.
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.
The msw library helps with mocking API calls from tests, letting you configure a server 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 the Router and the Providers 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!