Testowanie jednostkowe w natywnych aplikacjach React
Opublikowany: 2022-03-10React Native to jeden z najczęściej używanych frameworków do tworzenia aplikacji mobilnych. Ten samouczek jest skierowany do programistów, którzy chcą rozpocząć testowanie tworzonych przez siebie aplikacji React Native. Wykorzystamy framework testowania Jest i Enzyme.
W tym artykule poznamy podstawowe zasady testowania, poznamy różne biblioteki do testowania aplikacji i zobaczymy, jak testować jednostki (lub komponenty) aplikacji React Native. Pracując z aplikacją React Native ugruntujemy naszą wiedzę na temat testowania.
Uwaga: Podstawowa znajomość JavaScript i React Native będzie bardzo przydatna podczas pracy z tym samouczkiem.
Co to jest testowanie jednostkowe?
Testowanie jednostkowe to poziom testowania, na którym testowane są poszczególne komponenty oprogramowania. Robimy to, aby każdy element działał zgodnie z oczekiwaniami. Komponent to najmniejsza testowalna część oprogramowania.
Aby to zilustrować, utwórzmy komponent Button
i zasymuluj test jednostkowy:
import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;
Ten komponent Button
zawiera tekst i funkcję onPress
. Przetestujmy ten składnik, aby zobaczyć, na czym polega testowanie jednostkowe.
Najpierw utwórzmy plik testowy o nazwie Button.test.js
:
it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });
Tutaj testujemy, aby sprawdzić, czy nasz składnik Button
renderuje się tak, jak powinien na wszystkich ekranach aplikacji. Na tym właśnie polega testowanie jednostkowe: testowanie komponentów aplikacji, aby upewnić się, że działają tak, jak powinny.
Testowanie jednostkowe w natywnych aplikacjach React
Aplikację React Native można przetestować za pomocą różnych narzędzi, z których niektóre to:
- Sterownik sieciowy
To narzędzie testowe typu open source dla aplikacji Node.js służy również do testowania aplikacji React Native. - Koszmar
Automatyzuje to operacje testowe w przeglądarce. Zgodnie z dokumentacją „celem jest udostępnienie kilku prostych metod, które naśladują działania użytkownika (takich jakgoto
,type
iclick
), z interfejsem API, który wydaje się synchroniczny dla każdego bloku skryptów, a nie głęboko zagnieżdżonymi wywołaniami zwrotnymi”. - Żart
Jest to jedna z najpopularniejszych bibliotek testowych i ta, na której skupimy się dzisiaj. Podobnie jak React, jest utrzymywany przez Facebooka i został stworzony, aby zapewnić konfigurację „zerową” dla maksymalnej wydajności. - Mokka
Mocha to popularna biblioteka do testowania aplikacji React i React Native. Stało się narzędziem testowym wybieranym przez programistów ze względu na łatwość konfiguracji i użytkowania oraz szybkość działania. - Jaśmin
Zgodnie z jego dokumentacją, Jasmine jest opartym na zachowaniu frameworkiem programistycznym do testowania kodu JavaScript.
Wprowadzenie do żartu i enzymu
Zgodnie z jego dokumentacją „Jest to zachwycający framework do testowania JavaScript z naciskiem na prostotę”. Działa z zerową konfiguracją. Po instalacji (przy użyciu menedżera pakietów, takiego jak npm lub Yarn), Jest jest gotowy do użycia, bez konieczności wykonywania innych instalacji.
Enzyme to framework testujący JavaScript dla aplikacji React Native. (Jeśli pracujesz z React, a nie React Native, dostępny jest przewodnik.) Użyjemy Enzyme do testowania jednostek wyjściowych naszej aplikacji. Dzięki niemu możemy symulować runtime aplikacji.
Zacznijmy od założenia naszego projektu. Będziemy używać aplikacji Done With It na GitHub. To rynek aplikacji React Native. Zacznij od sklonowania go, przejdź do folderu i zainstaluj pakiety, uruchamiając następujące polecenie dla npm…
npm install
… lub to dla przędzy:
yarn install
To polecenie zainstaluje wszystkie pakiety w naszej aplikacji. Gdy to zrobimy, przetestujemy spójność interfejsu użytkownika naszej aplikacji za pomocą migawek, omówionych poniżej.
Migawki i konfiguracja Jest
W tej sekcji przetestujemy pod kątem dotyku użytkownika i interfejsu użytkownika komponentów aplikacji, testując migawki przy użyciu Jest.
Zanim to zrobimy, musimy zainstalować Jest i jego zależności. Aby zainstalować Jest for Expo React Native, uruchom następujące polecenie:
yarn add jest-expo --dev
Instaluje jest-expo
w katalogu naszej aplikacji. Następnie musimy zaktualizować nasz plik package.json
, aby mieć skrypt testowy:
"scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }
Dodając to polecenie, mówimy Jest, który pakiet zarejestrować w naszej aplikacji i gdzie.
Następnym krokiem jest dodanie do naszej aplikacji innych pakietów, które pomogą Jest w przeprowadzeniu kompleksowego testu. Dla npm uruchom to…
npm i react-test-renderer --save-dev
… a w przypadku przędzy to:
yarn add react-test-renderer --dev
Nadal mamy małą konfigurację do wykonania w naszym pliku package.json
. Zgodnie z dokumentacją Expo React Native, musimy dodać konfigurację transformIgnorePattern
, która zapobiega uruchamianiu testów w Jest za każdym razem, gdy plik źródłowy pasuje do testu (tj. jeśli test zostanie wykonany i podobny plik zostanie znaleziony w node modules
projektu).
"jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }
Teraz utwórzmy nowy plik o nazwie App.test.js
, aby napisać nasz pierwszy test. Sprawdzimy, czy nasza App
ma w swoim drzewie jeden element potomny:
import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });
Teraz uruchom yarn test
lub jego odpowiednik npm. Jeśli App.js
ma pojedynczy element potomny, nasz test powinien zaliczyć, co zostanie potwierdzone w interfejsie wiersza poleceń.
W powyższym kodzie zaimportowaliśmy Reacta i React
react-test-renderer
, które renderują nasze testy dla Expo
. Przekonwertowaliśmy drzewo komponentów <App />
na JSON, a następnie poprosiliśmy Jest o sprawdzenie, czy zwrócona liczba komponentów podrzędnych w JSON jest równa oczekiwanej przez nas liczbie.
Więcej testów migawek
Jak stwierdza David Adeneye:
„Test migawki zapewnia, że interfejs użytkownika (UI) aplikacji internetowej nie zmienia się nieoczekiwanie. Przechwytuje kod komponentu w danym momencie, dzięki czemu możemy porównać komponent w jednym stanie z dowolnym innym możliwym stanem, jaki może przyjąć.”
Odbywa się to zwłaszcza wtedy, gdy projekt obejmuje style globalne, które są używane w wielu komponentach. Napiszmy test migawki dla App.js
, aby przetestować jego spójność interfejsu użytkownika:
it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });
it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });
Dodaj to do testów, które już napisaliśmy, a następnie uruchom yarn test
(lub jego odpowiednik npm). Jeśli nasz test przejdzie pomyślnie, powinniśmy zobaczyć to:
PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s
To mówi nam, że nasze testy zakończyły się pomyślnie i ile czasu zajęły. Twój wynik będzie wyglądał podobnie, jeśli testy zakończą się pomyślnie.

Przejdźmy do naśladowania niektórych funkcji w Jest.
Mocowanie wywołań API
Według dokumentacji Jest:
Funkcje atrapy umożliwiają testowanie powiązań między kodem poprzez wymazanie rzeczywistej implementacji funkcji, przechwytywanie wywołań funkcji (i parametrów przekazywanych w tych wywołaniach), przechwytywanie wystąpień funkcji konstruktorów podczas tworzenia instancji za pomocą `new` i umożliwienie testowania konfiguracja czasu zwracanych wartości.
Mówiąc najprościej, makieta to kopia obiektu lub funkcji bez rzeczywistego działania tej funkcji. Naśladuje tę funkcję.
Mocki pomagają nam testować aplikacje na wiele sposobów, ale główną korzyścią jest to, że zmniejszają naszą potrzebę zależności.
Mocki można zwykle wykonywać na dwa sposoby. Jednym z nich jest stworzenie funkcji atrapy, która jest wstrzykiwana do testowanego kodu. Drugim jest napisanie funkcji atrapy, która nadpisuje pakiet lub zależność dołączoną do komponentu.
Większość organizacji i programistów woli pisać ręczne makiety imitujące funkcjonalność i wykorzystywać fałszywe dane do testowania niektórych komponentów.
React Native obejmuje fetch
w obiekcie globalnym. Aby uniknąć wykonywania prawdziwych wywołań API w naszym teście jednostkowym, kpimy z nich. Poniżej znajduje się sposób na zakpicie wszystkich, jeśli nie większości, naszych wywołań API w React Native i bez konieczności tworzenia zależności:
global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };
Tutaj napisaliśmy funkcję, która raz próbuje pobrać API. Po wykonaniu tej czynności zwraca obietnicę, a po jej rozwiązaniu zwraca treść w formacie JSON. Przypomina to próbną odpowiedź na nieudaną transakcję pobierania — zwraca błąd.
Poniżej znajduje się komponent product
naszej aplikacji, zawierający obiekt product
i zwracający informacje jako props
.
import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;
Wyobraźmy sobie, że próbujemy przetestować wszystkie komponenty naszego produktu. Bezpośredni dostęp do naszej bazy danych nie jest możliwym rozwiązaniem. W tym miejscu do gry wchodzą kpiny. W poniższym kodzie próbujemy zasymulować komponent produktu, używając Jest do opisania obiektów w komponencie.
describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });
Używamy describe
z Jest, aby dyktować testy, które chcemy wykonać. W pierwszym teście sprawdzamy, czy obiekt, który mijamy, jest równy rekwizytom, z których się wyśmiewaliśmy.
W drugim teście przekazujemy rekwizyty customer
, aby upewnić się, że jest to produkt i pasuje do naszych makiety. W ten sposób nie musimy testować wszystkich komponentów naszego produktu, a także możemy zapobiegać błędom w naszym kodzie.
Mocowanie zewnętrznych żądań API
Do tej pory prowadziliśmy testy wywołań API z innymi elementami w naszej aplikacji. Teraz wyśmiejmy zewnętrzne wywołanie API. Będziemy używać Axios. Aby przetestować zewnętrzne wywołanie API, musimy kpić z naszych żądań, a także zarządzać otrzymywanymi odpowiedziami. Zamierzamy użyć axios-mock-adapter
do wykpiwania Axiosa. Najpierw musimy zainstalować axios-mock-adapter
, uruchamiając poniższe polecenie:
yarn add axios-mock-adapter
Następną rzeczą do zrobienia jest stworzenie naszych makiet:
import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });
Tutaj wywołujemy ApiClient
i przekazujemy do niego instancję Axios, aby zakpić poświadczenia użytkownika. Używamy pakietu o nazwie faker.js do generowania fałszywych danych użytkownika, takich jak adres e-mail i hasło.
Makieta zachowuje się tak, jak oczekujemy od API. Jeśli żądanie się powiedzie, otrzymamy odpowiedź z kodem stanu 200 dla OK. I otrzymamy kod statusu 400 za złe żądanie do serwera, który zostanie wysłany z JSON z komunikatem „Niepoprawna nazwa użytkownika i hasło”.
Teraz, gdy nasz makieta jest gotowa, napiszmy test dla zewnętrznego żądania API. Tak jak poprzednio, będziemy używać migawek.
it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });
W tym miejscu testujemy pomyślne logowanie przy użyciu poprawnych poświadczeń, używając natywnej funkcji async await
JavaScript w celu przechowywania danych wejściowych. Tymczasem funkcja authenticateUser
z Jest uwierzytelnia żądanie i upewnia się, że pasuje do naszych wcześniejszych migawek. Następnie testujemy pod kątem nieudanego logowania w przypadku błędnych poświadczeń, takich jak adres e-mail lub hasło, i jako odpowiedź wysyłamy błąd.
Teraz uruchom yarn test
lub npm test
. Jestem pewien, że wszystkie twoje testy zdadzą.
Zobaczmy, jak testować komponenty w bibliotece zarządzania stanami Redux.
Testowanie akcji Redux i reduktorów za pomocą migawek
Nie można zaprzeczyć, że Redux jest jednym z najczęściej używanych menedżerów stanu dla aplikacji React. Większość funkcji w Redux obejmuje dispatch
, która jest funkcją sklepu Redux, która służy do wyzwalania zmiany stanu aplikacji. Testowanie Redux może być trudne, ponieważ actions
Redux szybko rosną pod względem rozmiaru i złożoności. Dzięki migawkom Jest staje się to łatwiejsze. Większość testów z Redux sprowadza się do dwóch rzeczy:
- Aby przetestować
actions
, tworzymyredux-mock-store
i wysyłamy akcje. - Aby przetestować reduktory, importujemy
reducer
i przekazujemy do niego obiekt stanu i akcji.
Poniżej znajduje się test Redux z migawkami. Będziemy testować akcje wysyłane przez uwierzytelnienie użytkownika podczas SIGN-IN
i sprawdzając, jak akcja LOGOUT
jest obsługiwana przez reduktor user
.
import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });
W pierwszym teście opisujemy uwierzytelnianie logowania i tworzenie pozorowanego sklepu. Robimy to najpierw importując mockStore
z Redux, a następnie importując metodę o nazwie testUser
z Jest, aby pomóc nam zakpić użytkownika. Następnie testujemy, kiedy użytkownik pomyślnie loguje się do aplikacji przy użyciu adresu e-mail i hasła, które są zgodne z tymi w naszym magazynie migawek. Migawka zapewnia więc, że obiekty wprowadzane przez użytkownika pasują do siebie za każdym razem, gdy uruchamiany jest test.
W drugim teście sprawdzamy, kiedy użytkownik się wyloguje. Gdy nasza migawka reduktora potwierdzi, że użytkownik się wylogował, powraca do początkowego stanu aplikacji.
Następnie testujemy, uruchamiając yarn test
. Jeśli testy przeszły pomyślnie, powinniśmy zobaczyć następujący wynik:
PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s
Wniosek
Dzięki Jest testowanie aplikacji React Native nigdy nie było łatwiejsze, zwłaszcza w przypadku migawek, które zapewniają spójność interfejsu użytkownika niezależnie od globalnych stylów. Ponadto Jest pozwala nam symulować określone wywołania API i moduły w naszej aplikacji. Możemy pójść dalej, testując komponenty aplikacji React Native.
Dalsze zasoby
- „Praktyczny przewodnik testowania aplikacji React Native za pomocą Jest”, David Adeneye, Smashing Magazine
- Jest dokumentacja
- „Testing With Jest”, dokumentacja Expo React Native
- „Nauka testowania React Native z Jest”, Jason Gaare