Рубрики
Без рубрики

Устройство тестирования приложения redux

Я расскажу о проблемах, с которыми я столкнулся, когда пытаюсь проверить код redux и как я их решил. Я также расскажу о том, как убедиться, что ваши погрузчики WebPack не будут вмешиваться в ваши тесты.

Автор оригинала: Yang-Hsing.

На кодаменте мы используем реагирование на сборку некоторый из нашего Сервисы Отказ Вот вторая часть серии RACT & Redux, написанная одним из наших Devs!

За последние несколько месяцев я использовал React & Redux для нескольких проектов, и я хотел бы написать о своем опыте Redux в этой 3-частей серии:

  • Привет redux: Эта статья представит вас к Redux и пройти причины, которые я думаю, что это круто
  • Серверный рендеринг : Это будет руководство о том, как использовать redux и Реагистрационный маршрутизатор Чтобы сделать серверный рендеринг
  • Устройство тестирования : Я расскажу о проблемах, с которыми я столкнулся при попытке проверить код Redux и как я их решил. Я также расскажу о том, как убедиться, что ваши погрузчики WebPack не будут вмешиваться в ваши тесты.

В этой части я пойду за тестирование подразделения.

На тестирование подразделения

Тестирование подразделения является большим предметом для покрытия: из того, почему вам необходимо установить тест, чтобы настроить среду тестирования, для разработки тестируемой архитектуры для вашей кодовой базы и т. Д. В большинстве случаев способ тестирования во многом зависеть от того, какие библиотеки или Фрезы используются в вашем производственном коде. Например, приложение, построенное с React + Redux, будет тестирована намного иначе, чем приложение, которое было построено с Angularjs.

В этом разделе я пойду через несколько основных концепций в модульном тестировании, а после этого я сосредоточусь на вопросах, связанных с взаимодействием и Redux.

Что такое тестирование подразделения и как это отличается от других форм тестирования?

Тестирование подразделения – это тип автоматизированного теста, который является наиболее близким к самому коду, так как цель теста устройства часто является классом, функциями, компонентом реагирования и т. Д. – Вещи, которые вы можете разделить с кодом. Другими словами, модульные тесты написаны для разработчиков.

По сравнению с тестированием приема на более высоком уровне, сам код приемки – это немного дальше от вашего производственного кода, в качестве признаков Test Test Tests (например, была успешной регистрацией, вы можете создать пользователь через некоторую кнопку и т. Д.). В основном, тесты приема пишется для тех, кто все это говорит, не может кодировать (например, менеджеры проектов XD).

Например, цель теста подразделения может быть следующим:

  • Чтобы убедиться, что Допродаверщик вернется новый Вопрос:| как состояние после получения Вопрос_loaded мероприятие.

В качестве альтернативы, тест на принятие будет нацелен на что-то следующее:

  • Чтобы убедиться, когда пользователь нажимает ссылку вопроса, пользователь будет доведен до Вопрос:| Страница, где все вопросы были оказаны.

Почему нам нужно написать модульные тесты?

Устройства могут помочь нам, убедитесь, что наш код ведет себя как ожидалось

В качестве кодовой базы становится все больше и получает больше сотрудников, становится непрактичным, чтобы вручную проверить, ведет ли каждая функция или класс, как и ожидалось. Если бы у нас были автоматические тесты, чтобы проверить, будут ли вещи работать, как ожидалось каждый раз, когда мы меняем наш код, это значительно сократит время, которое мы провели на отладке. Другими словами, модульные тесты позволят каждому сотрудничеству сделать смелые изменения в код, потому что тесты легко позволят им знать, случайно они что-то сломали.

С помощью модульных испытаний разработчики могут рефакторизовать их код без необходимости беспокоиться о нарушении его, а во времени это становится хорошим циклом, который сделает код STABLER. Более того, тесты единиц помогут уменьшить время разработки добавления новых функций или внесения изменений в код. В целом, агрегатные испытания поставляются со многими удивительными преимуществами.

В дополнение к стабилизаторам кода вы также можете просмотреть тесты подразделения в качестве документации, которые не будут лгать.

Я считаю, что большинство из нас имеют опыт возвращения к коду через 2 недели и удивляетесь, кто, черт возьми, написал этот ужасный беспорядок, только для использования Гит виноват Чтобы узнать, что вы были тем, кто его написал.

Если вы забывчивый человек, модульные тесты могут играть роль напоминания. То есть, если вы забыли, почему функция была закодирована или как его использовать, вы можете взглянуть на код тестирования, поскольку он даст вам демонстрацию.

Достаточно. Как насчет redux?

При добавлении тестов в приложение Redux вы можете нарушить шаги следующим образом:

  1. Выберите структуру тестирования, а также библиотеку утверждения и издевательства (например, Mocha , Жасмин и т. Д.) Настройте настройки. Начните писать тесты, которые можно разбить в:

  2. Тесты действий

    • Редукторные тесты
    • Промежуточные программы
    • Компонентные тесты
    • Выясните, как разобраться с WebPack

В этой статье я пропущу первый шаг, поскольку вы можете найти детали конфигурации здесь Отказ Следующее руководство также предполагает, что читатели имеют базовое понимание того, как работают Mocha и Chai API.

Время написать некоторые тесты!

… не XD

Прежде чем написать тесты, нам нужно разбить процесс на несколько шагов:

  • Определите, какой предмет должен быть проверен
  • Определите, что должно быть проверено поведение (предмет)
  • Настройка среда тестирования, которая выполнит поведение, которое мы хотим проверить
  • Убедитесь, что . что результат как ожидается

Я не буду приносить все свой код, поэтому не стесняйтесь ссылаться на исходный код здесь если нужно.

Тест действия

Под дизайном Redux действия относительно просты. На поверхности действие просто возвращает объект действий.

Например, если у нас есть Действия/Вопросы.js у него есть LoadQuestion Функция, которую мы смотрим тестировать:

import { CALL_API } from 'middleware/api'; 

export const LOADED_QUESTIONS = 'LOADED_QUESTIONS'; 
export function loadQuestions() { 
  return { 
    [CALL_API]: { 
      method: 'get', 
      url: 'http://localhost:3000/questions', 
      successType: LOADED_QUESTIONS 
    } 
  }; 
}

Таким образом, на основе вышеупомянутых шагов мы знаем следующее:

  • Что нужно проверить: Вопрос Действие Создатель

  • какое поведение должно быть проверено

    • Загрузка () вернет объект, который содержит Call_api ключ, и он должен содержать данные, которые мы ожидаем

Таким образом, наш тест на действие выглядел таким в Спецификация/Действия/Вопросы. test.js

import { CALL_API } from 'middleware/api';

// setup
import * as actionCreator from 'actions/questions';
import * as ActionType from 'actions/questions';

describe('Action::Question', function(){
  describe('#loadQuestions()', function(){
    it('returns action CALL_API info', function(){
      // execute
      let action = actionCreator.loadQuestions();

      // verify
      expect(action[CALL_API]).to.deep.equal({
        method: 'get',
        url: 'http://localhost:3000/questions',
        successType: ActionType.LOADED_QUESTIONS
      });
    });
  });
});

Тест редуктора

В Redux редуктор действует как функция, которая соединяет состояние к действию, а затем возвращает новое состояние. Другими словами, Редуктор получит Действие , а затем решить, как государство должно быть изменено на основе Действие и текущее состояние.

Например, скажем, у нас есть редуктор Редуктор/вопросы .js :

import * as ActionType from 'actions/questions';

function questionsReducer (state = [], action) {
  switch(action.type) {
    case ActionType.LOADED_QUESTIONS:
      return action.response;
      break;
    default:
      return state;
  }
}

export default questionsReducer;

На этот раз мы определили шаги как:

  • Что нужно проверить: Вопрос редуктор

  • Какое поведение необходимо проверить:

    • Когда Вопрос-вопросы получает Loaded_questions Действие, это установит Action.response Как новое состояние
    • Когда встречался с типом действия, он не распознает, Вопрос-вопросы вернет пустой массив.

И, таким образом, наш тест на редуктор выглядел бы так в Спецификация/редукторы/вопросы. test.js :

import questionReducer from 'reducers/questions';
import * as ActionType from 'actions/questions';

describe('Reducer::Question', function(){
  it('returns an empty array as default state', function(){
    // setup
    let action = { type: 'unknown' };

    // execute
    let newState = questionReducer(undefined, { type: 'unknown' });

    // verify
    expect(newState).to.deep.equal([]);
  });

  describe('on LOADED_QUESTIONS', function(){
    it('returns the response in given action', function(){
      // setup
      let action = {
        type: ActionType.LOADED_QUESTIONS,
        response: { responseKey: 'responseVal' }
      };

      // execute
      let newState = questionReducer(undefined, action);

      // verify
      expect(newState).to.deep.equal(action.response);
    });
  });
});

Промежуточный тест

В приложении Redux промежуточное программное обеспечение несет ответственность за перехватывание действия, которое было отправлено редуктору и изменением исходного поведения действия до достижения редуктора. Сама промежуточное программное обеспечение является функцией с подписью, которая выглядит так:

function(store) {
  return function(next) {
    return function(action) {
      // middleware behavior...
    };
  };
}

Если вы используете синтаксис ES6, это будет выглядеть чище, хотя его природа так же сложна:

store => next => action => {
  // middleware behavior...
}

Во всяком случае, я думаю, что это одна из самых элегантных функций в redux. Я объясню, почему подробно в следующей части, но сейчас давайте сначала позаботиться о тесте.

Допустим, у нас есть промежуточное программное обеспечение API Middleware/API.JS.

import { camelizeKeys } from 'humps';
import superAgent from 'superagent';
import Promise from 'bluebird';
import _ from 'lodash';

export const CALL_API = Symbol('CALL_API');

export default store => next => action => {
  if ( ! action[CALL_API] ) {
    return next(action);
  }
  let request = action[CALL_API];
  let { getState } = store;
  let deferred = Promise.defer();
  let { method, url, successType } = request;
  superAgent[method](url)
    .end((err, res)=> {
      if ( !err ) {
        next({
          type: successType,
          response: res.body
        });
      }
      deferred.resolve();
    });

  return deferred.promise;
};

По сути, промежуточное ПО следующее:

  • перехватывает действие с Call_api ключ
  • на основе URL В Call_api Значение (скажем, это называется Запрос ), Метод отправит запрос вызова API HTTP на сервер
  • Как только API успешно называется промежуточное программное обеспечение A request.successtype действие
  • Само промежуточное программное обеспечение вернет обещание. Это обещание будет решено после того, как API будет успешно назван. (Более полная версия того, что происходит, так это то, что она должна иметь соответствующую обработку ошибок, но чтобы все могли простые, мы пропустим эту часть).

Таким образом, внедрение выше шагов, мы можем определить тест следующим образом:

  • Что нужно проверить: API промежуточное программное обеспечение
  • Какое поведение необходимо проверить:
    • промежуточное программное обеспечение будет игнорировать действия без Call_api ключ
    • промежуточное программное обеспечение отправит сервер вызов API на основе действие [call_api]
    • промежуточное программное обеспечение будет Отправка действие [call_api] .successtype Событие после успешного запроса
    • промежуточное программное обеспечение разрешит обещание промежуточного программного обеспечения после успешного запроса

Как таковой, наш тестовый код SPEC/MARDWARE/API.TEST.JS будет выглядеть следующим образом:

import nock from 'nock';
import apiMiddleware, { CALL_API } from 'middleware/api';

describe('Middleware::Api', function(){
  let store, next;
  let action;
  let successType = 'ON_SUCCESS';
  let url = 'http://the-url/path';

  beforeEach(function(){
    store = {};
    next = sinon.stub();
    action = {
      [CALL_API]: {
        method: 'get',
        url,
        successType
      }
    };
  });

  describe('when action is without CALL_API', function(){
    it('passes the action to next middleware', function(){
      action = { type: 'not-CALL_API' };
      apiMiddleware(store)(next)(action);
      expect(next).to.have.been.calledWith(action);
    });
  });

  describe('when action is with CALL_API', function(){
    let nockScope;
    beforeEach(function(){
      nockScope = nock(http://the-url)
                    .get('/path');
    });
    afterEach(function(){
      nock.cleanAll();
    });
    it('sends request to path with query and body', function(){
      nockScope = nockScope.reply(200, { status: 'ok' });

      apiMiddleware(store)(next)(action);

      nockScope.done();
    });

    it('resolves returned promise when response when success', function(){
      nockScope = nockScope.reply(200, { status: 'ok' });

      let promise = apiMiddleware(store)(next)(action);

      return expect(promise).to.be.fulfilled;
    });
    it('dispatch successType with response when success', function(done){
      nockScope = nockScope.reply(200, { status: 'ok' });
      let promise = apiMiddleware(store)(next)(action);

      promise.then(()=> {
        expect(next).to.have.been.calledWith({
          type: successType,
          response: { status: 'ok' }
        });
        done();
      });
    });
  });
});

Этот тест более сложный, чем предыдущие, как Nock это библиотека, используемая для проверки запросов HTTP на Node.js. Nock выходит из этой статьи, так что давайте просто предположим, что вы знакомы с Nock.js XD (если нет, не стесняйтесь проверить Этот урок Node.js на тестировании HTTP-запросов с Nock.js).

В любом случае, кроме Nock Давайте объясним код выше более подробно:

Прежде всего, мы гнездемся каждый Rebedeach внутри Опишите , так как это позволяет каждому Опишите иметь свой собственный контекст. Кроме того, мы также можем использовать локальную переменную внутри Опишите Функция, так что тест внутри того же Опишите может поделиться переменной. (Например, Nockscope имел доступ только к коду внутри Когда действие с Call_api Опишите Блок).

Во-вторых, нам нужно понять Как выполнить промежуточное ПО в среде тестирования.

Поскольку промежуточное программное обеспечение является функцией, хотя и один, который завернут магазин и Следующий Отказ Чтобы проверить его, нам просто нужно вызывать обернутую функцию с издевательствами магазин и Следующий один за другим.

apiMiddleware(store)(next)(action);

Наконец, отправить успех с ответом, когда успех имеет асинхронное поведение. По умолчанию Mocha не будет ждать асинхронного кода, чтобы закончить выполнение. Это означает, что перед выполнением асинхронного обратного вызова, тестовый случай Mocha ( IT () ) уже закончится, и поэтому мы не сможем проверить некоторые поведения, которые происходят после выполнения асинхронного кода. Мы можем легко решить эту проблему, добавив сделано Аргумент функции после Это И моча ждет, пока сделано выполняется до того, как он завершит тестовый случай. Таким образом, мы можем позвонить сделано После того, как асинхронный обратный вызов был выполнен, чтобы убедиться, что Mocha завершит тестовый случай в точке, мы ожидаем, что это закончится.

Тест компонента

В Redux мы различаем компоненты реагирования на два типа: умный компонент и A глупый тупой компонент. Умный компонент относится к компоненту, который подключен к Redux, в то время как тупой компонент – это тот, который является исключительно продиктованным реквизитом.

Технически, тупые тесты компонентов не связаны напрямую для Redux, так как вы можете рассмотреть их стандартными компонентами реагирования. Тестирование компонентов обычно имеет отношение к издеванию, и я пробудую только по основаниям в этом разделе.

С другой стороны, умные компоненты в основном похожи с глупыми компонентами, но с добавлением соединить поведение, в котором он будет:

  1. вводить как опора Ключи, необходимые составляющей из Store.GetState () , в котором ключи выбраны с помощью функции MapstatetoProps Отказ

  2. Введите части действия в компонент как пропры

По словам Официальная документация Redux , рекомендуемый способ проверить умные компоненты – это работать вокруг соединить и напрямую проверить компонентную часть.

Например, скажем, у нас есть компонент Контейнеры/question.js :

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { loadQuestions } from 'actions/questions';
import { Link } from 'react-router';
import _ from 'lodash';

class Question extends Component {
  static fetchData({ store }) {
    return store.dispatch(loadQuestions());
  }

  componentDidMount() {
    this.props.loadQuestions();
  }
  render() {
    return (
      

Question

{ _.map(this.props.questions, (q)=> { return (

{ q.content }

); }) } Back to Home

); } } function mapStateToProps (state) { return { questions: state.questions }; } export { Question }; export default connect(mapStateToProps, { loadQuestions })(Question);

Здесь мы использовали React-Addons-Test-Utils Запросить и проверить содержание компонента.

Позвольте мне объяснить часть, где компонент оказывает ссылка обратно в / :

Поскольку наша цель тестирования является Компонент вопросов Мы игнорируем, как работают другие компоненты. Мы заботимся о отношениях между Вопрос:| Компонент и другие компоненты. Принимая Вопрос:| В качестве примера мы не хотим напрямую сделать другой компонент ( ссылка ), потому что могут возникнуть проблемы, пытаясь сделать другие компоненты. В этих случаях нам будет трудно определить, является ли это Вопрос:| проблема или проблема другого компонента.

Тем не менее, мы обычно визуализируем другие компоненты в компоненте, поэтому для нас довольно распространено, чтобы попасть в проблемы из-за этого. Одним из решений состоит в том, чтобы издеваться от других компонентов из теста с __Rowire__ Отказ

Link = React.createClass({
  render() {
    return (

MOCK COMPONENT CLASS

) } }); Container.__Rewire__('Link', Link);

Таким образом, когда Вопрос:| Получается в тесте, Ссылка Мы видим, будет фиктивным компонентом, а не реальным Ссылка составная часть. Следовательно, мы можем идти вперед и проверить отношения пользовательских интерфейсов между Вопрос:| Компонент и Ссылка составная часть.

let doc = TestUtils.renderIntoDocument();
let link = TestUtils.findRenderedComponentWithType(doc, Link);

expect(link).not.to.be.undefined;
expect(link.props.to).to.equal('/');

Как бороться с вашим WebPack

У WebPack есть много волшебных погрузчиков, которые позволят нам требуется Множество различных типов объектов JavaScript, таких как изображения, листы стилей и т. Д. При выборе загрузчиков нам нужно взвешивать компромиссы. Pro Loader состоит в том, что он упаковывает все, что нам нужно вместе, и Con в том, что вы можете запустить только код погрузчика в вашем WebPack.

Если наше приложение необходимо только быть выполнено на стороне клиента, погрузчики не должны быть проблемой. Однако, если тот же код должен работать на стороне сервера для универсального рендеринга, то это означает, что загрузчик, который вы выбрали, придется упаковать код сервера отдельно. Короче говоря, вам понадобится два файла конфигурации WebPack для универсального приложения.

Лично я предпочитаю избегать погрузчиков, которые потребуют файла конфигурации на стороне сервера и клиента, если мне нужно сделать универсальную рендеринг. Упрошено, чтобы сервер запустить код самостоятельно, а не через веб-папак.

Пример ниже код, который необходимо выполнить только на стороне клиента (это не Используется в Этот пример ), в котором я использовал загрузчик URL для требующих изображений.

let SignupModal = React.createClass({
  render() {
    let cmLogo = require('Icons/white/icon_codementor.png');
    
    ...
  }
})

Как вы можете видеть, когда этот компонент отображается, это потребует изображения и затем будет приступить к счастливому перерыву при тестировании под средой Nodejs.

Один из способов решения этого – это обернуть функцию вокруг требуемого изображения. Таким образом, мы можем издеваться над функцией в нашей среде тестирования.

И, таким образом, наш компонент теперь должен выглядеть так:

import requireImage from 'lib/requireImage';

let SignupModal = React.createClass({
  render() {
    let cmLogo = requireImage('Icons/white/icon_codementor.png');
    
    ...
  }
})

В котором Треминование просто простой требуется :

/lib/requireimage.js.

export default function(path) {
  return require(path);
}

Таким образом, мы можем издеваться от Треминование В нашем тесте:

describe('Component SignupModal', function(){
  let requireImage;
  beforeEach(function() {
    requireImage = function() {}
    SignupModal.__Rewire__('requireImage', requireImage);
  });
  
  it('can be rendered', function() {
    // now we can render here
  });
});

Заключение

Тестирование требует усилий и практики, но мы можем лучше понять, как наш код работает через процесс написания тестов. Большую часть времени код, который легко проверить, это код, который был хорошо структурирован.

Поскольку проект растет по размеру и сотрудникам, в конечном итоге ошибки начнут перегружать развитие, если на месте нет тестов. Кроме того, как только мы получим более знакомые с помощью тестов на написание, время, которое требуется для записи тестов, займет гораздо меньше времени, необходимое для отладки.

Redux предназначен для того, чтобы облегчить писать тесты, и я думаю, что это один из аспектов, которые делают Redux так хорошо.

Если вам понравилось то, что вы узнали о тестировании … Что вы ждете?

Эта статья была первоначально опубликована на китайском здесь от Ян-HSING LIN и был переведен Yi-jirr Chen. Не стесняйтесь оставить комментарий ниже, если у вас есть какие-либо отзывы или вопросы!