Надежные

React-компоненты

 

Модульное тестирование

React-компонентов

Размахнин Никита

front-end developer Fullstack Development

razmakhnin9402@gmail.com

Содержание

  • Кратко о юнит-тестах и React.js по отдельности;

  • Одно из главных преимуществ React.js;

  • Чем и как тестировать React-компоненты

Терминология

React.js —

  1. Библиотека;
  2. Буква V в аббревиатуре MVC (Model-View-Controller);
  3. JSX - return (<MyComponent myProp={myValue} />);
  4. Композиция;
  5. “Реактивный” однонаправленный поток данных.

Терминология

Модульные тесты (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 компонента списка

Интеграционное тестирование

В ходе тестирования компонента                    проверяется две основных функциональности:

  1. При выборе поста в списке он отображается детально;

  2. При создании нового поста из формы он заносится в список.
<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

Ссылки

  1. Документация Enzyme: http://airbnb.io/enzyme/docs/api/index.html;

  2. Тестовый проект:

Nikita

By julya_key09

Nikita

  • 471