(Jest) Snapshot Testing

Snapshot Testing ?

Le soucis des tests

Mise en place

Courbe d'apprentissage

Écriture

Alternative : Snapshot Testing

Gold(en) Master Testing

Entrée fixe → [application]  Sortie fixe

Boîte noire

Gold Master

Pourquoi on en reparle aujourd'hui ?

Snapshot Testing avec React

Jest

Painless JavaScript Testing

Test d'un composant react

(react-test-render)

export default class Link extends React.Component {
  ...
  render() {
    return (
      <a
        className={this.state.class}
        href={this.props.page || '#'}
        onMouseEnter={this._onMouseEnter}
        onMouseLeave={this._onMouseLeave}>
        {this.props.children}
      </a>
    );
  }
}

Composant

import renderer from 'react-test-renderer';
test('Link renders correctly', () => {
  const tree = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  ).toJSON();
  expect(tree).toMatchSnapshot();
});
exports[`Link renders correctly 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function bound _onMouseEnter]}
  onMouseLeave={[Function bound _onMouseLeave]}>
  Facebook
</a>
`;

Test

Snapshot

Sortie console

Github Pull Request

Life Cycle React

class Progress extends React.Component {
    ...
    componentWillReceiveProps(nextProps) {
        this.setState({
          position: Progress.getProgressPosition(nextProps.value)
        });
    }
    ...
    render() {
        const progressStyle = { 
            transform: `scaleX(${this.state.position})` 
        };

        return (
          <div>
            <div style={progressStyle} />
          </div>
        );
    }
}
test('Progress Control goes from 250 to 500', () => {
  const progressConfig = {
    min: 0,
    max: 1000,
    value: 250
  };

  const component = renderer.create(
    <Progress {...progressConfig} />
  );

  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  progressConfig.value = 500;
  component.update(<Progress {...progressConfig} />);

  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});
exports[`test Progress Control goes from 250 to 500 1`] = `
<div
  className="test">
  <div
    className="test-progress"
    style={
      Object {
        "transform": "scaleX(0.25)"
      }
    } />
</div>
`;

exports[`test Progress Control goes from 250 to 500 2`] = `
<div
  className="test">
  <div
    className="test-progress"
    style={
      Object {
        "transform": "scaleX(0.5)"
      }
    } />
</div>
`;

React DOM / DOM Ref

class Slider extends Progress {
    ...
    handleDown(event) {
        this.manageListeners(ReactDOM.findDOMNode(this).ownerDocument);
    }
    ...
}
test('Slider Control renders correctly', () => {
  const sliderConfig = {
    min: 0,
    max: 1000,
    value: 250
  };

  const tree = renderer.create(
    <Slider {...sliderConfig} />
  ).toJSON();

  expect(tree).toMatchSnapshot();
});
Using Jest CLI v14.1.0, jasmine2, babel-jest
 FAIL  app/components/controls/slider/tests/Slider.test.js (0s)
 Runtime Error
  - Invariant Violation: ReactCompositeComponent: injectEnvironment() can only be called once.
        at invariant (node_modules/fbjs/lib/invariant.js:38:15)
        at Object.ReactComponentEnvironment.injection.injectEnvironment (node_modules/react/lib/ReactComponentEnvironment.js:36:60)
        at Object.inject (node_modules/react/lib/ReactDefaultInjection.js:79:28)
        at Object.<anonymous> (node_modules/react/lib/ReactDOM.js:28:23)
        at Object.<anonymous> (node_modules/react-dom/index.js:3:18)
        at Object.<anonymous> (app/components/controls/slider/Slider.jsx:2:43)
        at Object.<anonymous> (app/components/controls/slider/tests/Slider.test.js:3:41)
        at process._tickCallback (internal/process/next_tick.js:103:7)
1 test suite failed, 0 tests passed (0 total in 1 test suite, run time 1.62s)
jest.mock('react-dom');

Enzyme

( jsdom + enzyme-to-json )

import { mount } from 'enzyme';
import { mountToJson } from 'enzyme-to-json';

test('Slider Control renders correctly', () => {
  const sliderConfig = {
    min: 0,
    max: 1000,
    value: 250
  };

  const wrapper = mount(
    <Slider {...sliderConfig} />
  );

  expect(mountToJson(wrapper)).toMatchSnapshot();
});

Snapshot Testing Redux and More

JSON et Objets

describe('Configurator', () => {
    it('should get config in json format', () => {
      Configurator.getJson().then((config) => {
        expect(JSON.parse(config)).toMatchSnapshot();
      })
    });
});

Redux Reducer

describe('MY_TESTED_ACTION', () => {
    it('should do something', () => {
      const action = {
        type: MY_TESTED_ACTION,
        payload: {...}
      };

      expect(reducer(initialState, action)).toMatchSnapshot();
    });
});

The Good, The Bad

& The Ugly

The Good

  • Simple à mettre en place.
  • Simple à appréhender
  • Simple à écrire

The Bad

  • Pas de TDD & DDD
  • Manque de documentation
  • Le snapshot ne suffit pas...

The Ugly

  • autoMock
  • -u
  • ⚠️ Attention aux Review ⚠️

The Volkswagen Syndrome

(Jest) Snapshot Testing

By nafresne

(Jest) Snapshot Testing

  • 828