Надежные
React-компоненты
Модульное тестирование
React-компонентов
Размахнин Никита
front-end developer Fullstack Development
razmakhnin9402@gmail.com
Содержание
-
Кратко о юнит-тестах и React.js по отдельности;
-
Одно из главных преимуществ React.js;
-
Чем и как тестировать React-компоненты
Терминология
React.js —
- Библиотека;
- Буква V в аббревиатуре MVC (Model-View-Controller);
- JSX - return (<MyComponent myProp={myValue} />);
- Композиция;
- “Реактивный” однонаправленный поток данных.
Терминология
Модульные тесты (unit-tests) —
1. Это код, который проверяет другой код;
2. Проверка модуля в изоляции от остальных частей системы на некотором множестве ожидаемых (и не очень) ситуаций.
Модульные тесты
Защищают основной код, определяя и проверяя поведение модуля в большинстве предвиденных разработчиком ситуаций.
Облегчают дальнейшую поддержку и расширение программы.
основной код
тесты
Тестирование view
Тестировать представление сложно, потому что существует слишком много состояний этого представления, большая часть которых зачастую не учитывается.
В представлении возможны связи между программно слабо связанными частями интерфейса, и их взаимодействие также сложно для тестирования.
React-компоненты прекрасно тестируются
1. Инкапсуляция;
2. Разделение ответственности;
3. Слабое зацепление.
Используемые инструменты и технологии
Karma — сборщик тестов;
Karma
PhantomJS
Mocha
Chai
Sinon
Enzyme
PhantomJS — имплементация браузерного окружения;
Mocha — тестовый фреймоворк;
Chai — библиотека с тестовыми утверждениями;
Enzyme — ...
Enzyme.js
Библиотека с тестовыми инструментами от ребят из Airbnb.
/* Testing with Enzyme */
const component = shallow(<PostsList items={fixtureItems} />);
const items = component.find(PostItem);
expect(items).to.have.lengthOf(fixtureItems.length);
/* Testing with plain TestUtils */
const component = ReactTestUtils.renderIntoDocument(<PostsList items={fixtureItems} />);
const items = ReactTestUtils.scryRenderedComponentsWithType(component, PostItem);
expect(items).to.have.lengthOf(fixtureItems.length);
Чтобы не быть голословным…
<List items={posts} />
<Post
title=’Post title’
content={text} />
<Form onSubmit={handler} />
<Blog />



Тестируемый react-компонент
1. Имеет мало зависимостей от внешнего кода;
2. Зависим лишь от свойств (props) и состояния (state);
3. Не взаимодействует с DOM-фрагментами, не относящимися к этому компоненту;
4. Легковесный атомарный элемент системы с необходимой логикой.
“Поверхностное”
тестирование
// Component declaration
class PostsList extends Component {
render() {
return (
<div>
{
items.map((item, index) =>
<Item key={index} {...item} />)
}
</div>
);
}
}
// Test: shallow rendering
const component = shallow(
<PostsList items={fixtureItems} />
);
component.debug();
/*
<div>
<Item … />
<Item … />
…
<Item … />
</div>
*/
С чего начать...
const component = shallow(<Post />);
const title = component.findWhere(el => el.node === 'No title');
const content = component.findWhere(el => el.node === 'No content');
expect(title).to.have.lengthOf(1);
expect(content).to.have.lengthOf(1);
Тестируем компонент без заданных входных свойств:
- должна отобразиться “заглушка” для заголовка;
- должна отобразиться “заглушка” для содержимого
Тестирование на необходимом наборе свойств
const component = shallow(<Post title={post.title} content={post.content} />);
const title = component.findWhere(el => el.node === post.title);
const content= component.findWhere(el => el.node === post.content);
expect(title).to.have.lengthOf(1);
expect(content).to.have.lengthOf(1);
Тестирование функций обратного вызова
const onSubmitSpy = sinon.spy();
const component = shallow(<Form onSubmit={onSubmitSpy} />);
titleInput.simulate(‘change’, { target: { value: newPost.title } });
// … set state manually or simulate change events on inputs
component.simulate('submit');
// asserts
expect(onClickSpy.calledOnce).to.be.true;
expect(onClickSpy.firstCall.args[0]).to.deep.equal(newPost);
Тестирование поведения
const component = mount(<PostsList items={fixtureItems} />);
const item = component.find(PostItem).at(2);
item.simulate('click');
expect(item.prop(‘className’).indexOf('selected') >= 0).to.be.true;
expect(component.state(‘selectedItem)).to.deep.equal(fixtureItems[2]);
При нажатии на элемент в списке:
-
устанавливается класс ‘selected’ для этого элемента;
- данные элемента заносятся в state компонента списка
Интеграционное тестирование
В ходе тестирования компонента проверяется две основных функциональности:
-
При выборе поста в списке он отображается детально;
- При создании нового поста из формы он заносится в список.
<Blog />
Интеграционное тестирование
Создание нового поста через форму:
const component = mount(<Blog />);
/* ... Get referernces on all components, need for test case */
/* ... Enter values into inputs and simulate submit event on form */
expect(component.find(Item)).to.have.lengthOf(posts.length + 1);
expect(newItem.prop('title')).to.be.equal(newPost.title);
/* ... Asserts on other props of new post in list */
Интеграционное тестирование
Выбор поста из списка:
const component = mount(<Blog />);
/* ... Get referernces on all components, need for test case */
item.simulate('click');
expect(
post.findWhere(el => el.node.textContent === item.prop('title'))
).to.have.lengthOf(1);
expect(
post.findWhere(el => el.node.textContent === item.prop(content))
).to.have.lengthOf(1);
Спасибо
Размахнин Никита
front-end developer Fullstack Development
razmakhnin9402@gmail.com
Ссылки
-
Документация Enzyme: http://airbnb.io/enzyme/docs/api/index.html;
-
Тестовый проект:
Nikita
By julya_key09
Nikita
- 471