Πώς να ξεκινήσετε τη δοκιμή των εφαρμογών σας React χρησιμοποιώντας τη βιβλιοθήκη δοκιμών React και το Jest

Η δοκιμή θεωρείται συχνά ως κουραστική διαδικασία. Είναι επιπλέον κωδικός που πρέπει να γράψετε, και σε ορισμένες περιπτώσεις, για να είμαι ειλικρινής, δεν χρειάζεται. Αλλά κάθε προγραμματιστής πρέπει να γνωρίζει τουλάχιστον τα βασικά των δοκιμών. Αυξάνει την εμπιστοσύνη στα προϊόντα που κατασκευάζουν, και για τις περισσότερες εταιρείες, είναι απαίτηση.

Στον κόσμο του React, υπάρχει μια καταπληκτική βιβλιοθήκη που ονομάζεται η react-testing-libraryοποία σας βοηθά να δοκιμάσετε τις εφαρμογές σας React πιο αποτελεσματικά. Το χρησιμοποιείτε με το Jest.

Σε αυτό το άρθρο, θα δούμε τα 8 απλά βήματα που μπορείτε να ακολουθήσετε για να ξεκινήσετε να δοκιμάζετε τις Εφαρμογές React σαν αφεντικό.

  • Προαπαιτούμενα
  • Βασικά
  • Τι είναι το React Testing Library;
  • 1. Πώς να δημιουργήσετε ένα δοκιμαστικό στιγμιότυπο;
  • 2. Δοκιμή στοιχείων DOM
  • 3. Δοκιμαστικά γεγονότα
  • 4. Δοκιμή ασύγχρονων ενεργειών
  • 5. Δοκιμή React Redux
  • 6. Έλεγχος πλαισίου αντιδράσεων
  • 7. Δοκιμή δρομολογητή αντιδραστηρίων
  • 8. Έλεγχος αιτήματος HTTP
  • Τελικές σκέψεις
  • Επόμενα βήματα

Προαπαιτούμενα

Αυτό το σεμινάριο προϋποθέτει ότι έχετε τουλάχιστον μια βασική κατανόηση του React. Θα επικεντρωθώ μόνο στο δοκιμαστικό μέρος.

Και για να ακολουθήσετε, πρέπει να κλωνοποιήσετε το έργο τρέχοντας στο τερματικό σας:

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

Στη συνέχεια, εκτελέστε:

 yarn 

Ή, εάν χρησιμοποιείτε NPM:

npm install 

Και αυτό είναι! Τώρα ας δούμε μερικά βασικά.

Βασικά

Ορισμένα βασικά πράγματα θα χρησιμοποιηθούν πολύ σε αυτό το άρθρο και η κατανόηση του ρόλου τους μπορεί να σας βοηθήσει με την κατανόησή σας.

it or test: περιγράφει το ίδιο το τεστ. Παίρνει ως παράμετροι το όνομα του τεστ και μια συνάρτηση που κρατά τις δοκιμές.

expect: η κατάσταση που πρέπει να περάσει η δοκιμή. Θα συγκρίνει τη ληφθείσα παράμετρο με έναν αντιστοιχιστή.

a matcher: μια συνάρτηση που εφαρμόζεται στην αναμενόμενη συνθήκη.

render: η μέθοδος που χρησιμοποιείται για την απόδοση ενός δεδομένου στοιχείου.

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() }) }); 

Όπως μπορείτε να δείτε, περιγράφουμε τη δοκιμή με it, στη συνέχεια, χρήση renderγια την εμφάνιση του στοιχείου της εφαρμογής και αναμένουμε ότι asFragment()ταιριάζει toMatchSnapshot()(ο αντιστοιχιστής που παρέχεται από το jest-dom).

Παρεμπιπτόντως, η renderμέθοδος επιστρέφει διάφορες μεθόδους που μπορούμε να χρησιμοποιήσουμε για να δοκιμάσουμε τις δυνατότητές μας. Χρησιμοποιήσαμε επίσης την καταστροφή για να πάρουμε τη μέθοδο.

Τούτου λεχθέντος, ας προχωρήσουμε και μάθετε περισσότερα για το React Testing Library στην επόμενη ενότητα.

Τι είναι η βιβλιοθήκη δοκιμών αντιδράσεων;

Το React Testing Library είναι ένα πολύ ελαφρύ πακέτο που δημιουργήθηκε από τον Kent C. Dodds. Είναι αντικατάσταση του Enzyme και παρέχει λειτουργίες ελαφρού βοηθητικού προγράμματος πάνω από react-domκαι react-dom/test-utils.

Η βιβλιοθήκη δοκιμών React είναι μια βιβλιοθήκη δοκιμών DOM, πράγμα που σημαίνει ότι, αντί να ασχολείται με περιπτώσεις παρουσιασμένων στοιχείων React, χειρίζεται στοιχεία DOM και πώς συμπεριφέρονται μπροστά σε πραγματικούς χρήστες.

Είναι μια υπέροχη βιβλιοθήκη, είναι σχετικά εύκολο να ξεκινήσετε τη χρήση και ενθαρρύνει καλές πρακτικές δοκιμών. Σημείωση - μπορείτε επίσης να το χρησιμοποιήσετε χωρίς Jest.

"Όσο περισσότερο τα τεστ σας μοιάζουν με τον τρόπο που χρησιμοποιείται το λογισμικό σας, τόσο μεγαλύτερη αυτοπεποίθηση μπορούν να σας δώσουν."

Ας αρχίσουμε λοιπόν να το χρησιμοποιούμε στην επόμενη ενότητα. Παρεμπιπτόντως, δεν χρειάζεται να εγκαταστήσετε πακέτα, καθώς create-react-appσυνοδεύεται από τη βιβλιοθήκη και τις εξαρτήσεις της.

1. Πώς να δημιουργήσετε ένα δοκιμαστικό στιγμιότυπο

Ένα στιγμιότυπο, όπως υποδηλώνει το όνομα, μας επιτρέπει να αποθηκεύσουμε το στιγμιότυπο ενός δεδομένου στοιχείου. Βοηθά πολύ όταν ενημερώνετε ή κάνετε κάποια αναδιαμόρφωση και θέλετε να λάβετε ή να συγκρίνετε τις αλλαγές.

Τώρα, ας πάρουμε ένα στιγμιότυπο του App.jsαρχείου.

  • 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() }) }); 

Για να πάρουμε ένα στιγμιότυπο, πρέπει πρώτα να εισαγάγουμε renderκαι cleanup. Αυτές οι δύο μέθοδοι θα χρησιμοποιηθούν πολύ σε αυτό το άρθρο.

render, όπως μπορείτε να μαντέψετε, βοηθά στην απόδοση ενός στοιχείου React Και cleanupμεταδίδεται ως παράμετρος για afterEachνα καθαρίσετε τα πάντα μετά από κάθε δοκιμή για να αποφύγετε διαρροές μνήμης.

Στη συνέχεια, μπορούμε να αποδώσουμε το στοιχείο της εφαρμογής renderκαι να επιστρέψουμε asFragmentως επιστρεφόμενη τιμή από τη μέθοδο. Και τέλος, βεβαιωθείτε ότι το τμήμα του στοιχείου εφαρμογής ταιριάζει με το στιγμιότυπο.

Τώρα, για να εκτελέσετε τη δοκιμή, ανοίξτε το τερματικό σας και μεταβείτε στη ρίζα του έργου και εκτελέστε την ακόλουθη εντολή:

 yarn test 

Ή, εάν χρησιμοποιείτε npm:

 npm test 

Ως αποτέλεσμα, θα δημιουργήσει έναν νέο φάκελο __snapshots__και ένα αρχείο App.test.js.snapστο srcοποίο θα μοιάζει με αυτό:

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

Testing

`;

Και αν κάνετε άλλη αλλαγή App.js, η δοκιμή θα αποτύχει, επειδή το στιγμιότυπο δεν θα ταιριάζει πλέον με την κατάσταση. Για να περάσει, απλώς πατήστε uγια να το ενημερώσετε. Και θα έχετε το ενημερωμένο στιγμιότυπο App.test.js.snap.

Τώρα, ας προχωρήσουμε και ξεκινήστε να δοκιμάζουμε τα στοιχεία μας.

2. Δοκιμή στοιχείων DOM

Για να δοκιμάσουμε τα στοιχεία DOM, πρέπει πρώτα να κοιτάξουμε το TestElements.jsαρχείο.

  • 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

Εδώ, το μόνο πράγμα που πρέπει να διατηρήσετε είναι data-testid. Θα χρησιμοποιηθεί για την επιλογή αυτών των στοιχείων από το δοκιμαστικό αρχείο. Τώρα, ας γράψουμε τη δοκιμαστική μονάδα:

Ελέγξτε εάν ο μετρητής είναι ίσος με 0:

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) }); 

Όπως μπορείτε να δείτε, η σύνταξη είναι παρόμοια με την προηγούμενη δοκιμή. Η μόνη διαφορά είναι ότι χρησιμοποιούμε getByTestIdγια να επιλέξουμε τα απαραίτητα στοιχεία (θυμηθείτε το data-testid) και να ελέγξουμε εάν πέρασε το τεστ. Με άλλα λόγια, ελέγχουμε αν το περιεχόμενο του κειμένου

{ counter }

είναι ίσο με 0.

Ελέγξτε εάν τα κουμπιά είναι ενεργοποιημένα ή απενεργοποιημένα:

TestElements.test.js (προσθέστε το ακόλουθο μπλοκ κώδικα στο αρχείο)

 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() }); 

Εδώ, ως συνήθως, χρησιμοποιούμε getByTestIdγια να επιλέξουμε στοιχεία και να ελέγξουμε την πρώτη δοκιμή εάν το κουμπί έχει ένα disabledχαρακτηριστικό. Και για το δεύτερο, εάν το κουμπί είναι απενεργοποιημένο ή όχι.

Και αν αποθηκεύσετε το αρχείο ή εκτελέσετε ξανά στο τερματικό σας yarn test, ο έλεγχος θα περάσει.

Συγχαρητήρια! Η πρώτη σας δοκιμή έχει περάσει!

συγχαρητήρια

Τώρα, ας μάθουμε πώς να δοκιμάσουμε ένα συμβάν στην επόμενη ενότητα.

3. Δοκιμαστικά γεγονότα

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?

έτοιμος

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?

φυσικά

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!

ακόμη εδώ

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.

όχι φοβισμένος

Final Thoughts

Η βιβλιοθήκη δοκιμών React είναι ένα εξαιρετικό πακέτο για τη δοκιμή εφαρμογών React. Μας δίνει πρόσβαση σε jest-domαντιστοιχιστές που μπορούμε να χρησιμοποιήσουμε για να δοκιμάσουμε τα συστατικά μας πιο αποτελεσματικά και με καλές πρακτικές. Ας ελπίσουμε ότι αυτό το άρθρο ήταν χρήσιμο και θα σας βοηθήσει να δημιουργήσετε ισχυρές εφαρμογές React στο μέλλον.

Μπορείτε να βρείτε το ολοκληρωμένο έργο εδώ

Ευχαριστώ που το διαβάσατε!

Διαβάστε περισσότερα άρθρα - Εγγραφείτε στο newsletter μου - Ακολουθήστε με στο twitter

Μπορείτε να διαβάσετε άλλα άρθρα όπως αυτό στο blog μου.

Επόμενα βήματα

React Testing βιβλιοθήκη εγγράφων

React Testing Library Cheatsheet

Cheat φύλλο Jest DOM

Έγγραφα Jest