So starten Sie das Testen Ihrer React Apps mithilfe der React Testing Library und des Jest

Testen wird oft als langwieriger Prozess angesehen. Es ist zusätzlicher Code, den Sie schreiben müssen, und in einigen Fällen wird er, um ehrlich zu sein, nicht benötigt. Aber jeder Entwickler sollte zumindest die Grundlagen des Testens kennen. Dies erhöht das Vertrauen in die von ihnen hergestellten Produkte und ist für die meisten Unternehmen eine Voraussetzung.

In der React-Welt gibt es eine erstaunliche Bibliothek namens the, mit react-testing-libraryder Sie Ihre React-Apps effizienter testen können. Du benutzt es mit Jest.

In diesem Artikel sehen wir die 8 einfachen Schritte, die Sie ausführen können, um Ihre React Apps wie ein Chef zu testen.

  • Voraussetzungen
  • Grundlagen
  • Was ist eine React Testing Library?
  • 1. Wie erstelle ich einen Testschnappschuss?
  • 2. Testen von DOM-Elementen
  • 3. Testen von Ereignissen
  • 4. Testen von asynchronen Aktionen
  • 5. Testen React Redux
  • 6. Testen des Reaktionskontexts
  • 7. React Router testen
  • 8. Testen der HTTP-Anforderung
  • Abschließende Gedanken
  • Nächste Schritte

Voraussetzungen

In diesem Tutorial wird davon ausgegangen, dass Sie mindestens ein grundlegendes Verständnis von React haben. Ich werde mich nur auf den Testteil konzentrieren.

Und um mitzumachen, müssen Sie das Projekt klonen, indem Sie es in Ihrem Terminal ausführen:

 git clone //github.com/ibrahima92/prep-react-testing-library-guide 

Führen Sie als Nächstes Folgendes aus:

 yarn 

Oder wenn Sie NPM verwenden:

npm install 

Und das ist es! Lassen Sie uns nun einige Grundlagen untersuchen.

Grundlagen

Einige wichtige Dinge werden in diesem Artikel häufig verwendet, und das Verständnis ihrer Rolle kann Ihnen beim Verständnis helfen.

it or test: beschreibt den Test selbst. Als Parameter werden der Name des Tests und eine Funktion verwendet, die die Tests enthält.

expect: die Bedingung, dass der Test bestanden werden muss. Der empfangene Parameter wird mit einem Matcher verglichen.

a matcher: Eine Funktion, die auf den erwarteten Zustand angewendet wird.

render: Die Methode zum Rendern einer bestimmten Komponente.

import React from 'react' import {render} from '@testing-library/react' import App from './App' it('should take a snapshot', () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) }); 

Wie Sie sehen können, beschreiben wir den Test mit it, renderum die App-Komponente anzuzeigen und zu erwarten, dass sie asFragment()übereinstimmt toMatchSnapshot()(der von jest-dom bereitgestellte Matcher).

Übrigens gibt die renderMethode mehrere Methoden zurück, mit denen wir unsere Funktionen testen können. Wir haben auch Destrukturierung verwendet, um die Methode zu erhalten.

Lassen Sie uns fortfahren und im nächsten Abschnitt mehr über die React Testing Library erfahren.

Was ist die React Testing Library?

Die React Testing Library ist ein sehr leichtes Paket, das von Kent C. Dodds erstellt wurde. Es ist ein Ersatz für Enzyme und bietet Licht Utility - Funktionen auf der Oberseite react-domund react-dom/test-utils.

Die React Testing Library ist eine DOM-Testbibliothek. Dies bedeutet, dass anstelle von Instanzen gerenderter React-Komponenten DOM-Elemente behandelt werden und wie sie sich vor echten Benutzern verhalten.

Es ist eine großartige Bibliothek, die (relativ) einfach zu bedienen ist und gute Testpraktiken fördert. Hinweis - Sie können es auch ohne Scherz verwenden.

"Je mehr Ihre Tests der Art und Weise ähneln, wie Ihre Software verwendet wird, desto mehr Vertrauen können sie Ihnen geben."

Beginnen wir also im nächsten Abschnitt damit. Sie müssen übrigens keine Pakete installieren, da create-react-appdie Bibliothek und ihre Abhängigkeiten mitgeliefert werden.

1. So erstellen Sie einen Testschnappschuss

Mit einem Snapshot können wir, wie der Name schon sagt, den Snapshot einer bestimmten Komponente speichern. Es ist sehr hilfreich, wenn Sie ein Update durchführen oder Refactoring durchführen und die Änderungen abrufen oder vergleichen möchten.

Lassen Sie uns nun einen Schnappschuss der App.jsDatei machen.

  • App.test.js
import React from 'react' import {render, cleanup} from '@testing-library/react' import App from './App' afterEach(cleanup) it('should take a snapshot', () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) }); 

Um einen Snapshot erstellen, müssen wir zunächst importieren renderund cleanup. Diese beiden Methoden werden in diesem Artikel häufig verwendet.

renderWie Sie vielleicht erraten haben, hilft dies beim Rendern einer React-Komponente. Und cleanupwird als Parameter übergeben, afterEachum nach jedem Test einfach alles zu bereinigen, um Speicherlecks zu vermeiden.

Als Nächstes können wir die App-Komponente mit rendern renderund asFragmentals zurückgegebenen Wert von der Methode zurückerhalten . Stellen Sie schließlich sicher, dass das Fragment der App-Komponente mit dem Snapshot übereinstimmt.

Um den Test auszuführen, öffnen Sie jetzt Ihr Terminal, navigieren Sie zum Stammverzeichnis des Projekts und führen Sie den folgenden Befehl aus:

 yarn test 

Oder wenn Sie npm verwenden:

 npm test 

Als Ergebnis wird es einen neuen Ordner erstellen __snapshots__und eine Datei App.test.js.snapin der srcdie wie folgt aussehen:

  • App.test.js.snap
// Jest Snapshot v1, //goo.gl/fbAQLP exports[`Take a snapshot should take a snapshot 1`] = ` 

Testing

`;

Wenn Sie eine weitere Änderung vornehmen, schlägt App.jsder Test fehl, da der Schnappschuss nicht mehr der Bedingung entspricht. Um es zu bestehen, drücken uSie einfach , um es zu aktualisieren. Und Sie haben den aktualisierten Schnappschuss in App.test.js.snap.

Lassen Sie uns nun fortfahren und unsere Elemente testen.

2. Testen von DOM-Elementen

Um unsere DOM-Elemente zu testen, müssen wir uns zuerst die TestElements.jsDatei ansehen .

  • TestElements.js
import React from 'react' const TestElements = () => { const [counter, setCounter] = React.useState(0) return (  

{ counter }

setCounter(counter + 1)}> Up setCounter(counter - 1)}>Down ) } export default TestElements

Hier müssen Sie nur noch behalten data-testid. Es wird verwendet, um diese Elemente aus der Testdatei auszuwählen. Schreiben wir nun den Unit-Test:

Testen Sie, ob der Zähler gleich 0 ist:

TestElements.test.js

import React from 'react'; import { render, cleanup } from '@testing-library/react'; import TestElements from './TestElements' afterEach(cleanup); it('should equal to 0', () => { const { getByTestId } = render(); expect(getByTestId('counter')).toHaveTextContent(0) }); 

Wie Sie sehen können, ist die Syntax dem vorherigen Test ziemlich ähnlich. Der einzige Unterschied besteht darin, dass wir getByTestIddie erforderlichen Elemente auswählen (beachten Sie die data-testid) und prüfen, ob sie den Test bestanden haben. Mit anderen Worten, wir prüfen, ob der Textinhalt

{ counter }

ist gleich 0.

Testen Sie, ob die Schaltflächen aktiviert oder deaktiviert sind:

TestElements.test.js (Fügen Sie der Datei den folgenden Codeblock hinzu.)

 it('should be enabled', () => { const { getByTestId } = render(); expect(getByTestId('button-up')).not.toHaveAttribute('disabled') }); it('should be disabled', () => { const { getByTestId } = render(); expect(getByTestId('button-down')).toBeDisabled() }); 

Hier getByTestIdwählen wir wie gewohnt Elemente aus und prüfen beim ersten Test, ob die Schaltfläche ein disabledAttribut hat. Und zum zweiten, ob die Taste deaktiviert ist oder nicht.

Wenn Sie die Datei speichern oder erneut in Ihrem Terminal ausführen yarn test, besteht der Test.

Glückwunsch! Dein erster Test ist bestanden!

congrats

Lassen Sie uns nun im nächsten Abschnitt lernen, wie Sie ein Ereignis testen.

3. Testen von Ereignissen

Before writing our unit tests, let's first check what the TestEvents.js looks like.

  • TestEvents.js
import React from 'react' const TestEvents = () => { const [counter, setCounter] = React.useState(0) return (  

{ counter }

setCounter(counter + 1)}> Up setCounter(counter - 1)}>Down ) } export default TestEvents

Now, let's write the tests.

Test if the counter increments and decrements correctly when we click on buttons:

TestEvents.test.js

import React from 'react'; import { render, cleanup, fireEvent } from '@testing-library/react'; import TestEvents from './TestEvents' afterEach(cleanup); it('increments counter', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('button-up')) expect(getByTestId('counter')).toHaveTextContent('1') }); it('decrements counter', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('button-down')) expect(getByTestId('counter')).toHaveTextContent('-1') }); 

As you can see, these two tests are very similar except the expected text content.

The first test fires a click event with fireEvent.click() to check if the counter increments to 1 when the button is clicked.

And the second one checks if the counter decrements to -1 when the button is clicked.

fireEvent has several methods you can use to test events, so feel free to dive into the documentation to learn more.

Now that we know how to test events, let's move on and learn in the next section how to deal with asynchronous actions.

4. Testing asynchronous actions

An asynchronous action is something that can take time to complete. It can be an HTTP request, a timer, and so on.

Now, let's check the TestAsync.js file.

  • TestAsync.js
import React from 'react' const TestAsync = () => { const [counter, setCounter] = React.useState(0) const delayCount = () => ( setTimeout(() => { setCounter(counter + 1) }, 500) ) return (  

{ counter }

Up setCounter(counter - 1)}>Down ) } export default TestAsync

Here, we use setTimeout() to delay the incrementing event by 0.5s.

Test if the counter is incremented after 0.5s:

TestAsync.test.js

import React from 'react'; import { render, cleanup, fireEvent, waitForElement } from '@testing-library/react'; import TestAsync from './TestAsync' afterEach(cleanup); it('increments counter after 0.5s', async () => { const { getByTestId, getByText } = render(); fireEvent.click(getByTestId('button-up')) const counter = await waitForElement(() => getByText('1')) expect(counter).toHaveTextContent('1') }); 

To test the incrementing event, we first have to use async/await to handle the action because, as I said earlier, it takes time to complete.

Next, we use a new helper method getByText(). This is similar to getByTestId(), except that getByText() selects the text content instead of id or data-testid.

Now, after clicking to the button, we wait for the counter to be incremented with waitForElement(() => getByText('1')). And once the counter incremented to 1, we can now move to the condition and check if the counter is effectively equal to 1.

That being said, let's now move to more complex test cases.

Are you ready?

ready

5. Testing React Redux

If you're new to React Redux, this article might help you. Otherwise, let's check what the TestRedux.js looks like.

  • TestRedux.js
import React from 'react' import { connect } from 'react-redux' const TestRedux = ({counter, dispatch}) => { const increment = () => dispatch({ type: 'INCREMENT' }) const decrement = () => dispatch({ type: 'DECREMENT' }) return (  

{ counter }

Up Down ) } export default connect(state => ({ counter: state.count }))(TestRedux)

And for the reducer:

  • store/reducer.js
export const initialState = { count: 0, } export function reducer(state = initialState, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1, } case 'DECREMENT': return { count: state.count - 1, } default: return state } } 

As you can see, there is nothing fancy – it's just a basic Counter Component handled by React Redux.

Now, let's write the unit tests.

Test if the initial state is equal to 0:

TestRedux.test.js

import React from 'react' import { createStore } from 'redux' import { Provider } from 'react-redux' import { render, cleanup, fireEvent } from '@testing-library/react'; import { initialState, reducer } from '../store/reducer' import TestRedux from './TestRedux' const renderWithRedux = ( component, { initialState, store = createStore(reducer, initialState) } = {} ) => { return { ...render({component}), store, } } afterEach(cleanup); it('checks initial state is equal to 0', () => { const { getByTestId } = renderWithRedux() expect(getByTestId('counter')).toHaveTextContent('0') }) 

There are a couple of things we need to import to test React Redux. And here, we create our own helper function renderWithRedux() to render the component since it will be used several times.

renderWithRedux() receives as parameters the component to render, the initial state, and the store. If there is no store, it will create a new one, and if it doesn't receive an initial state or a store, it returns an empty object.

Next, we use render() to render the component and pass the store to the Provider.

That being said, we can now pass the component TestRedux to renderWithRedux() to test if the counter is equal to 0.

Test if the counter increments and decrements correctly:

TestRedux.test.js (add the following code block to the file)

it('increments the counter through redux', () => { const { getByTestId } = renderWithRedux(, {initialState: {count: 5} }) fireEvent.click(getByTestId('button-up')) expect(getByTestId('counter')).toHaveTextContent('6') }) it('decrements the counter through redux', () => { const { getByTestId} = renderWithRedux(, { initialState: { count: 100 }, }) fireEvent.click(getByTestId('button-down')) expect(getByTestId('counter')).toHaveTextContent('99') }) 

To test the incrementing and decrementing events, we pass an initial state as a second argument to renderWithRedux(). Now, we can click on the buttons and test if the expected result matches the condition or not.

Now, let's move to the next section and introduce React Context.

React Router and Axios will come next – are you still with me?

of-course

6. Testing React Context

If you're new to React Context, check out this article first. Otherwise, let's check the TextContext.js file.

  • TextContext.js
import React from "react" export const CounterContext = React.createContext() const CounterProvider = () => { const [counter, setCounter] = React.useState(0) const increment = () => setCounter(counter + 1) const decrement = () => setCounter(counter - 1) return (    ) } export const Counter = () => { const { counter, increment, decrement } = React.useContext(CounterContext) return (  

{ counter }

Up Down ) } export default CounterProvider

Now, the counter state is managed through React Context. Let's write the unit test to check if it behaves as expected.

Test if the initial state is equal to 0:

TextContext.test.js

import React from 'react' import { render, cleanup, fireEvent } from '@testing-library/react' import CounterProvider, { CounterContext, Counter } from './TestContext' const renderWithContext = ( component) => { return { ...render(  {component} ) } } afterEach(cleanup); it('checks if initial state is equal to 0', () => { const { getByTestId } = renderWithContext() expect(getByTestId('counter')).toHaveTextContent('0') }) 

As in the previous section with React Redux, here we use the same approach, by creating a helper function renderWithContext() to render the component. But this time, it receives only the component as a parameter. And to create a new context, we pass CounterContext to the Provider.

Now, we can test if the counter is initially equal to 0 or not.

Test if the counter increments and decrements correctly:

TextContext.test.js (add the following code block to the file)

 it('increments the counter', () => { const { getByTestId } = renderWithContext() fireEvent.click(getByTestId('button-up')) expect(getByTestId('counter')).toHaveTextContent('1') }) it('decrements the counter', () => { const { getByTestId} = renderWithContext() fireEvent.click(getByTestId('button-down')) expect(getByTestId('counter')).toHaveTextContent('-1') }) 

As you can see, here we fire a click event to test if the counter increments correctly to 1 and decrements to -1.

That being said, we can now move to the next section and introduce React Router.

7. Testing React Router

If you want to dive into React Router, this article might help you. Otherwise, let's check the TestRouter.js file.

  • TestRouter.js
import React from 'react' import { Link, Route, Switch, useParams } from 'react-router-dom' const About = () =>

About page

const Home = () =>

Home page

const Contact = () => { const { name } = useParams() return

{name}

} const TestRouter = () => { const name = 'John Doe' return ( Home About Contact ) } export default TestRouter

Here, we have some components to render when navigating the Home page.

Now, let's write the tests:

  • TestRouter.test.js
import React from 'react' import { Router } from 'react-router-dom' import { render, fireEvent } from '@testing-library/react' import { createMemoryHistory } from 'history' import TestRouter from './TestRouter' const renderWithRouter = (component) => { const history = createMemoryHistory() return { ...render (  {component}  ) } } it('should render the home page', () => { const { container, getByTestId } = renderWithRouter() const navbar = getByTestId('navbar') const link = getByTestId('home-link') expect(container.innerHTML).toMatch('Home page') expect(navbar).toContainElement(link) }) 

To test React Router, we have to first have a navigation history to start with. Therefore we use createMemoryHistory() to well as the name guessed to create a navigation history.

Next, we use our helper function renderWithRouter() to render the component and pass history to the Router component. With that, we can now test if the page loaded at the start is the Home page or not. And if the navigation bar is loaded with the expected links.

Test if it navigates to other pages with the parameters when we click on links:

TestRouter.test.js (add the following code block to the file)

it('should navigate to the about page', ()=> { const { container, getByTestId } = renderWithRouter() fireEvent.click(getByTestId('about-link')) expect(container.innerHTML).toMatch('About page') }) it('should navigate to the contact page with the params', ()=> { const { container, getByTestId } = renderWithRouter() fireEvent.click(getByTestId('contact-link')) expect(container.innerHTML).toMatch('John Doe') }) 

Now, to check if the navigation works, we have to fire a click event on the navigation links.

For the first test, we check if the content is equal to the text in the About Page, and for the second, we test the routing params and check if it passed correctly.

We can now move to the final section and learn how to test an Axios request.

We're almost done!

still-here

8. Testing HTTP Request

As usual, let's first see what the TextAxios.js file looks like.

  • TextAxios.js
import React from 'react' import axios from 'axios' const TestAxios = ({ url }) => { const [data, setData] = React.useState() const fetchData = async () => { const response = await axios.get(url) setData(response.data.greeting) } return (  Load Data { data ? {data} : 

Loading...

} ) } export default TestAxios

As you can see here, we have a simple component that has a button to make a request. And if the data is not available, it will display a loading message.

Now, let's write the tests.

Test if the data are fetched and displayed correctly:

TextAxios.test.js

import React from 'react' import { render, waitForElement, fireEvent } from '@testing-library/react' import axiosMock from 'axios' import TestAxios from './TestAxios' jest.mock('axios') it('should display a loading text', () => { const { getByTestId } = render() expect(getByTestId('loading')).toHaveTextContent('Loading...') }) it('should load and display the data', async () => { const url = '/greeting' const { getByTestId } = render() axiosMock.get.mockResolvedValueOnce({ data: { greeting: 'hello there' }, }) fireEvent.click(getByTestId('fetch-data')) const greetingData = await waitForElement(() => getByTestId('show-data')) expect(axiosMock.get).toHaveBeenCalledTimes(1) expect(axiosMock.get).toHaveBeenCalledWith(url) expect(greetingData).toHaveTextContent('hello there') }) 

This test case is a bit different because we have to deal with an HTTP request. And to do that, we have to mock an axios request with the help of jest.mock('axios').

Now, we can use axiosMock and apply a get() method to it. Finally we will use the Jest function mockResolvedValueOnce() to pass the mocked data as a parameter.

With that, now for the second test we can click to the button to fetch the data and use async/await to resolve it. And now we have to test 3 things:

  1. If the HTTP request has been done correctly
  2. If the HTTP request has been done with the url
  3. If the data fetched matches the expectation.

And for the first test, we just check if the loading message is displayed when we have no data to show.

That being said, we're now done with the 8 simple steps to start testing your React Apps.

Don't be scared to test anymore.

not-scared

Final Thoughts

The React Testing Library is a great package for testing React Apps. It gives us access to jest-dom matchers we can use to test our components more efficiently and with good practices. Hopefully this article was useful, and it will help you build robust React apps in the future.

You can find the finished project here

Thanks for reading it!

Read more articles  -  Subscribe to my newsletter   -   Follow me on twitter

You can read other articles like this on my blog.

Next Steps

React Testing Library docs

React Testing Library Cheatsheet

Jest DOM matchers cheatsheet

Jest Docs