Кодовите фрагменти, които ще бъдат показани по-долу, не са достатъчни, за да накарат приложението да работи, тъй като разкриват само редовете, подходящи за публикацията в блога.

class App extends React.Component {
 constructor() {
 super();
componentDidMount() {
  Api.getContacts().then(data => 
 { this.setState({ contacts: data}) })
 }

Както можете да видите, компонентът App не извиква API само по себе си, но използва метода getContacts(), дефиниран в модула Api, за да го направи.

Обаждането към външен API е извлечено в отделен Api модул, което прави кода много по-лесен за тестване, тъй като можем да напишем ръчен макет за този модул; вижте повече по-долу. Освен това, той „диктува“ СУХИЯ дизайн, тъй като можем да очакваме, че когато бъдещите разработчици ще добавят повече API извиквания, те ще го направят в модула Api, като използват повторно части от вече съществуваща логика.

Ето как изглежда Api:

import axios from 'axios';
class Api {
staticgetContacts() {
 return axios.get('/contacts')
 .then((response) => {
 return response.data })
 }
staticaddContact(name, telephone) {
 return axios.post('/contacts/add', {
 "name": name,
 "telephone": telephone,
 })
 .then((response) => { return response.data })
 .catch((error) => { alert("Error in adding contact") })
 }
}

Ръчен макет на модула Api

Както бе споменато по-горе, възнамеряваме да тестваме дали контактите се изобразяват в DOM. Това, което прави тестването му трудно, е фактът, че контактите се извличат от външен ресурс. Този външен ресурс трябва да бъде подиграван.

Използването на истински API би ни лишило от запазването на контрол върху това какви данни идват в приложението ни по време на теста и може значително да увеличи времето за тестване. За да получим бързи и последователни входни данни за нашето приложение, трябва да симулираме извикването към външния ресурс и да контролираме какви данни се връщат от него.

Можем да го постигнем, като използваме едно от многото подигравателни решения с отворен код или изберем една от четирите подигравателни техники, изброени в Jest документацията. Най-популярните са Автоматичен макет – техника, при която създавате макет, за да шпионирате извикванията към конструктора на класа и всичките му методи, иРъчен макет, при който пишете ново поведение за модула.

От техниките, изброени в документите на Jest, избрах Manual Mocks, тъй като те позволяват на тестера да има пълен контрол върху внедряването и да се увери, че макетът Api може да се използва повторно и в други тестове.

По-долу е даден ръчен макет на модула Api. Той има методите getContacts() и addContact() като "истинския" Api модул, но вместо да прави истинско API извикване, има твърдо кодирани return стойности. Твърдо кодираните стойности приличат на това, което се очаква да бъде получено от истинското извикване на API.

import axios from 'axios';
class Api {
static getContacts() {
 return Promise.resolve([
  { name: "Bob", telephone: "11", id: 1 },
 { name: "Anna", telephone: "22", id: 2 }
 ])}
static addContact(name, telephone) {
 return Promise.resolve([
  { name: "Weronika", telephone: "123", id: 3 }
 ])}
export default Api

Както можете да видите, когато извиква метода getContacts(), модулът Api - вместо да направи истинско get повикване - връща Promise, което, когато бъде разрешено, съдържа data за два контакта: Bob и Anna. Методът addContact() работи по подобен начин.

Сега е време да поставите този макет във файловата структура. На същото ниво, на което се намира модулът, на който се подиграваме (в този случай това е Api), трябва да създадем директория __mocks__, където ще поставим макетния модул.

.
├── config
├── utils
│ ├── __mocks__
│ │ └── api.js // mock of real module
│ └── api.js // real module
├── node_modules
└── views

Време за тестване!

И сега е време за изплащане, тъй като ще (най-накрая) тестваме нашето приложение!

Първият тест би бил да се провери дали контактите се показват в DOM, когато се извличат от API.

Първият избор, който трябва да направим, е какъв ензимен метод искаме да използваме: shallow или mount? Тъй като както извикването на API, така и изобразяването на контакти се извършват в самия компонент App - с други думи, не се нуждаем от функционалности на друг компонент за извличане на контакти и изобразяване на тях - shallow ing ще бъде правилният избор.

Нашата настройка ще изглежда така:

import Adapter from 'enzyme-adapter-react-16';
import axios from 'axios';
import Enzyme, { shallow } from 'enzyme';
import React from 'react';
import Api from '../../Api';
import App from '../App.react';
jest.mock("../Api")
Enzyme.configure({adapter: new Adapter()});
describe('App', () => {
const app = shallow(<App />);
});

Използваме ръчен макет на модула Api, като извикваме jest.mock("../Api"). Кодът Enzyme.configure({adapter: new Adapter()}) е необходим за свързване на Enzyme с версията на React, която използваме, която е React 16.

Сега можем да напишем първия си тест. Само за да проверим дали макетът работи правилно, ще напишем тест, който проверява състоянието на app. Този тест е faaaaaar от идеален по две причини. На първо място,състоянието на тестване, а не поведението, е лоша практика, тъй като може да доведе до смесване на тестове с внедряване и ще направи тестовете много чувствителни към всички промени в дизайна на код. Например, ако един ден решим да започнем да използваме Redux вместо локално състояние, такъв тест за състояние ще стане остарял и което е по-важно, ще се провали. Второ, проверката дали състоянието съхранява данните от API чрез твърдо кодиране на върнатата стойност на API по същество е тестване на ситуация, която никога няма да се случи в реалния живот, тъй като извикването към реалния API е асинхронно, докато нашият тест се изпълнява в синхронна екосистема . Въпреки тези важни причини, аз вярвам, че писането на този тест (и изтриването му по-късно!) е добра отправна точка, за да видите дали макетът работи.

В този тест очакваме, че state на нашия app ще съдържа същите контакти, които макетът Api връща в data. Може да попитате „НО КАК!? Никъде не сме направили извикване на API; всичко, което направихме, беше shallowing на App компонента!' Тук съм с отговорите, не се притеснявайте. Всичко е така, защото методът shallow извиква метода componentDidMount на жизнения цикъл на React, вътре в който направихме извикването към API. Бум!

Стига с тестването на състоянието, тъй като това така или иначе е лоша практика: време е за поведенчески тестове! В теста по-долу вече не търсим промени в състоянието, а вместо това проверяваме дали две деца са поставени в contacts-list div след като приложението е mounted. Очакваме два контакта, защото това е броят на обектите, върнати от нашия макет Api.

describe('app', () => {
 it('has contacts in the conacts-list div', () => {
 expect
(app.find('.contacts-list').children().length).toEqual(2);
 })
 })

За да сме сигурни, че децата всъщност са контакти, можем да проверим дали имат HTML класа, който очакваме:

expect(app.find('.contacts-list').children().filter(".single-contact").length).toEqual(2);

За да бъдем още по-конкретни, можем да проверим точния текст на контактите в contacts-list, като изпълним следния тест:

expect(app.find('.contacts-list').render().text()).toEqual(
 "1. Bob 11 ✍ 💾 ❌ 2. Anna 22 ✍ 💾 ❌ "
);

В кода по-горе използвах друг ензимен метод, който е render(). Този метод се използва за изобразяване на React компоненти в статичен HTML, което ни позволява лесно да изследваме съдържанието на изобразения елемент.

Първоначално публикувано на https://medium.com на 21 ноември 2018 г.