Внедрение объекта response-intl в смонтированные компоненты Enzyme для тестирования

РЕДАКТИРОВАТЬ: Решено! Прокрутите вниз, чтобы найти ответ


В наших тестах компонентов нам нужно, чтобы они имели доступ к контексту react-intl. Проблема в том, что мы монтируем отдельные компоненты (с помощью _2 _) без их <IntlProvider /> родительской оболочки. Это решается путем обертывания поставщика, но тогда root указывает на экземпляр IntlProvider, а не на CustomComponent.

Документы Тестирование с помощью React-Intl: Enzyme все еще пустые.

‹CustomComponent /›

class CustomComponent extends Component {
  state = {
    foo: 'bar'
  }

  render() {
    return (
      <div>
        <FormattedMessage id="world.hello" defaultMessage="Hello World!" />
      </div>
    );
  }
}

Стандартный тестовый набор (желательно) (фермент + мокко + чай)

// This is how we mount components normally with Enzyme
const wrapper = mount(
  <CustomComponent
    params={params}
  />
);

expect( wrapper.state('foo') ).to.equal('bar');

Однако, поскольку наш компонент использует FormattedMessage как часть библиотеки react-intl, мы получаем эту ошибку при выполнении приведенного выше кода:

Uncaught Invariant Violation: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.


Оберните его IntlProvider

const wrapper = mount(
  <IntlProvider locale="en">
    <CustomComponent
      params={params}
    />
  </IntlProvider>
);

Это обеспечивает CustomComponent запрашиваемый intl контекст. Однако при попытке выполнить тестовые утверждения, подобные этим:

expect( wrapper.state('foo') ).to.equal('bar');

вызывает следующее исключение:

AssertionError: expected undefined to equal ''

Это, конечно, потому, что он пытается прочитать состояние IntlProvider, а не наш CustomComponent.


Попытки получить доступ к CustomComponent

Я безрезультатно пробовал следующее:

const wrapper = mount(
  <IntlProvider locale="en">
    <CustomComponent
      params={params}
    />
  </IntlProvider>
);


// Below cases have all individually been tried to call `.state('foo')` on:
// expect( component.state('foo') ).to.equal('bar');

const component = wrapper.childAt(0); 
> Error: ReactWrapper::state() can only be called on the root

const component = wrapper.children();
> Error: ReactWrapper::state() can only be called on the root

const component = wrapper.children();
component.root = component;
> TypeError: Cannot read property 'getInstance' of null

Возникает вопрос: Как мы можем смонтировать CustomComponent с intl контекстом, сохранив при этом возможность выполнять «корневые» операции с нашим CustomComponent?


person Mirage    schedule 04.05.2016    source источник


Ответы (1)


Я создал вспомогательные функции для исправления существующих функций Enzyme mount() и shallow(). Теперь мы используем эти вспомогательные методы во всех наших тестах, где мы используем компоненты React Intl.

Вы можете найти суть здесь: https://gist.github.com/mirague/c05f4da0d781a9b339b501f1d5d33c


Вот краткий код для обеспечения доступности данных:

helpers / intl-test.js

/**
 * Components using the react-intl module require access to the intl context.
 * This is not available when mounting single components in Enzyme.
 * These helper functions aim to address that and wrap a valid,
 * English-locale intl context around them.
 */

import React from 'react';
import { IntlProvider, intlShape } from 'react-intl';
import { mount, shallow } from 'enzyme';

const messages = require('../locales/en'); // en.json
const intlProvider = new IntlProvider({ locale: 'en', messages }, {});
const { intl } = intlProvider.getChildContext();

/**
 * When using React-Intl `injectIntl` on components, props.intl is required.
 */
function nodeWithIntlProp(node) {
  return React.cloneElement(node, { intl });
}

export default {
  shallowWithIntl(node) {
    return shallow(nodeWithIntlProp(node), { context: { intl } });
  },

  mountWithIntl(node) {
    return mount(nodeWithIntlProp(node), {
      context: { intl },
      childContextTypes: { intl: intlShape }
    });
  }
};

Пользовательский компонент

class CustomComponent extends Component {
  state = {
    foo: 'bar'
  }

  render() {
    return (
      <div>
        <FormattedMessage id="world.hello" defaultMessage="Hello World!" />
      </div>
    );
  }
}

CustomComponentTest.js

import { mountWithIntl } from 'helpers/intl-test';

const wrapper = mountWithIntl(
  <CustomComponent />
);

expect(wrapper.state('foo')).to.equal('bar'); // OK
expect(wrapper.text()).to.equal('Hello World!'); // OK
person Mirage    schedule 04.05.2016
comment
Используя ваш помощник выше, я получаю TypeError: (0 , _intl.mountWithIntl) is not a function при попытке импортировать mountWithIntl - person Jon Cursi; 06.05.2016
comment
Интересно, вы уверены, что ссылка на вспомогательный файл intl-test.js верна? Мне кажется, import не работает. Возможно, ваша require() установка не ищет .js файлы по умолчанию? Вы можете попробовать добавить расширение .js к вашему файлу импорта. - person Mirage; 06.05.2016
comment
Я попытался добавить ту же ошибку. Какая у вас настройка Babel? - person Jon Cursi; 07.05.2016
comment
Я решил использовать ваших исходных помощников, чтобы я мог оценить состояние дочерних компонентов - это потрясающая функция. Однако теперь я застрял на одной последней загвоздке: поиск переводов после монтирования компонента gist.github.com/mirague/ - person Jon Cursi; 08.05.2016
comment
Спасибо за этот ответ, он мне очень помог. Однако теперь я обнаружил, что мне нужно протестировать языковой стандарт, отличный от en. Изменение локали при создании экземпляра IntlProvider не совсем помогает. Например, создайте экземпляр с локалью 'fr' и попробуйте использовать formatNumber - в качестве десятичного разделителя по-прежнему используется точка, а не запятая. Как я могу гарантировать, что IntlProvider также загрузит требуемый языковой стандарт? - person Wayne Birch; 24.05.2017
comment
Если ссылка верна, все функции поступают в один экспорт, вы можете разделить экспорт вспомогательного файла, и тогда это сработало для меня! - person Shatayu Darbhe; 11.02.2019