Подтвердить что ты не робот

Как насмехаться с контекстом реакции-маршрутизатора

У меня довольно простой компонент реакции (Link wrapper, который добавляет "активный" класс, если маршрут активен):

import React, { PropTypes } from 'react';
import { Link } from 'react-router';

const NavLink = (props, context) => {
  const isActive = context.router.isActive(props.to, true);
  const activeClass = isActive ? 'active' : '';

  return (
    <li className={activeClass}>
      <Link {...props}>{props.children}</Link>
    </li>
  );
}

NavLink.contextTypes = {
  router: PropTypes.object,
};

NavLink.propTypes = {
  children: PropTypes.node,
  to: PropTypes.string,
};

export default NavLink;

Как я могу проверить это? Моя единственная попытка:

import NavLink from '../index';

import expect from 'expect';
import { mount } from 'enzyme';
import React from 'react';

describe('<NavLink />', () => {
  it('should add active class', () => {
    const renderedComponent = mount(<NavLink to="/home" />, { router: { pathname: '/home' } });
    expect(renderedComponent.hasClass('active')).toEqual(true);
  });
});

Он не работает и возвращает TypeError: Cannot read property 'isActive' of undefined. Это определенно нуждается в каком-то маршрутизаторе, но я понятия не имею, как его записать.

4b9b3361

Ответ 1

Спасибо @Elon Szopos за ваш ответ, но мне удается написать что-то гораздо более простое (следуя https://github.com/airbnb/enzyme/pull/62):

import NavLink from '../index';

import expect from 'expect';
import { shallow } from 'enzyme';
import React from 'react';

describe('<NavLink />', () => {
  it('should add active class', () => {
    const context = { router: { isActive: (a, b) => true } };
    const renderedComponent = shallow(<NavLink to="/home" />, { context });
    expect(renderedComponent.hasClass('active')).toEqual(true);
  });
});

Мне нужно изменить mount на shallow, чтобы не оценивать Link, что дает мне ошибку, связанную с реактивным маршрутизатором TypeError: router.createHref is not a function.

Я бы предпочел иметь "реальный" реактивный маршрутизатор, чем просто объект, но я понятия не имею, как его создать.

Ответ 2

Для ответного маршрутизатора v4 вы можете использовать <MemoryRouter>. Пример с AVA и ферментом:

import React from 'react';
import PropTypes from 'prop-types';
import test from 'ava';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { MemoryRouter as Router } from 'react-router-dom';

const mountWithRouter = node => mount(<Router>{node}</Router>);

test('submits form directly', t => {
  const onSubmit = sinon.spy();
  const wrapper = mountWithRouter(<LogInForm onSubmit={onSubmit} />);
  const form = wrapper.find('form');
  form.simulate('submit');

  t.true(onSubmit.calledOnce);
});

Ответ 3

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

Вы можете найти оболочку ниже:

import React, { PropTypes } from 'react'

export default class WithContext extends React.Component {
  static propTypes = {
    children: PropTypes.any,
    context: PropTypes.object
  }

  validateChildren () {
    if (this.props.children === undefined) {
      throw new Error('No child components were passed into WithContext')
    }
    if (this.props.children.length > 1) {
      throw new Error('You can only pass one child component into WithContext')
    }
  }

  render () {
    class WithContext extends React.Component {
      getChildContext () {
        return this.props.context
      }

      render () {
        return this.props.children
      }
    }

    const context = this.props.context

    WithContext.childContextTypes = {}

    for (let propertyName in context) {
      WithContext.childContextTypes[propertyName] = PropTypes.any
    }

    this.validateChildren()

    return (
      <WithContext context={this.props.context}>
        {this.props.children}
      </WithContext>
    )
  }
}

Здесь вы можете увидеть пример использования:

  <WithContext context={{ location: {pathname: '/Michael/Jackson/lives' }}}>
    <MoonwalkComponent />
  </WithContext>

  <WithContext context={{ router: { isActive: true }}}>
    <YourTestComponent />
  </WithContext>

И он должен работать так, как вы ожидали.

Ответ 4

Вы можете использовать https://github.com/pshrmn/react-router-test-context для этой цели

"Создайте объект псевдоконтекста, который дублирует структуру context.router React Router. Это полезно для мелкого модульного тестирования с Enzyme."

После установки вы сможете сделать что-то вроде

describe('my test', () => {
  it('renders', () => {
    const context = createRouterContext()
    const wrapper = shallow(<MyComponent />, { context })
  })
})

Ответ 5

Вам нужно знать разницу между mount и shallow. Официальная документация объяснила Неверный рендеринг:

При написании модульных тестов для React может оказаться полезным мелкий рендеринг. Мелкий рендеринг позволяет визуализировать компонент "один уровень глубины" и утверждать факты о том, что возвращает метод рендеринга, не беспокоясь о поведении дочерних компонентов, которые не являются экземплярами или оказаны. Это не требует DOM.

Тогда, без сложности, ни контексты:

   const renderedComponent = shallow(<NavLink to="/home" />);

вместо

   const renderedComponent = mount(<NavLink to="/home" />, { router: { pathname: '/home' } });

Это сработает! 🎉