Comment se moquer de réagir-routeur contexte

J'ai un composant react assez simple (link wrapper qui ajoute une classe 'active' si la route est active):

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;

Comment suis-je censé le tester? Ma seule tentative était:

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);
  });
});

Cela ne fonctionne pas et renvoie TypeError: Cannot read property 'isActive' of undefined. Il a certainement besoin d'un routeur se moquant, mais je n'ai aucune idée de comment l'écrire.

21
demandé sur jmarceli 2016-06-30 14:12:40

5 réponses

Merci @Elon Szopos pour votre réponse mais je parviens à écrire quelque chose de beaucoup plus simple (suivant 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);
  });
});

Je dois changer mount en shallow afin de ne pas évaluer Link ce qui me donne une erreur liée au react-router TypeError: router.createHref is not a function.

Je préférerais avoir un" vrai " routeur react plutôt qu'un simple objet mais je n'ai aucune idée de comment le créer.

18
répondu jmarceli 2016-06-30 11:59:13

Pour réagir routeur v4 vous pouvez utiliser un <MemoryRouter>. Exemple avec AVA et Enzyme:

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);
});
13
répondu walteronassis 2017-04-13 20:34:32

Tester les composants qui s'appuient sur le contexte peut être un peu délicat. Ce que j'ai fait était d'écrire un wrapper que j'ai utilisé dans mes tests.

Vous pouvez trouver le wrapper ci-dessous:

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>
    )
  }
}

Ici, vous pouvez voir un exemple d'utilisation:

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

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

Et cela devrait fonctionner comme prévu.

5
répondu Elod Szopos 2016-06-30 11:23:36

Vous pouvez utiliser https://github.com/pshrmn/react-router-test-context dans ce but précis

" Créez un objet pseudo-contexte qui duplique le contexte du routeur React.structure du routeur. Ceci est utile pour les tests unitaires peu profonds avec Enzyme."

Après l'avoir installé, vous pourrez faire quelque chose comme

describe('my test', () => {
  it('renders', () => {
    const context = createRouterContext()
    const wrapper = shallow(<MyComponent />, { context })
  })
})
3
répondu katzmopolitan 2017-04-04 22:12:58

, Vous devez connaître la différence entre mount & shallow . La documentation officielle a expliqué le rendu peu profond :

Lors de l'écriture de tests unitaires pour React, le rendu superficiel peut être utile. Le rendu superficiel vous permet de rendre un composant "un niveau profond" et affirmer des faits sur ce que retourne sa méthode de rendu, sans se soucier sur le comportement des composants enfants, qui ne sont pas instanciés ou rendre. Cela ne nécessite pas un DOM.

Alors, sans complexité, ni contextes:

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

Au Lieu de

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

Ça va marcher!

0
répondu Abdennour TOUMI 2017-09-06 13:17:08