📖 profile

Jest is a facebook testing framework that integrates Mocha, Chai, Jsdom, Sinon, and more.

Enzyme is Airbnb’s open source test framework for React. Its Api is as flexible as Jquery, because Enzyme uses cheerio library to parse HTML. Cheerio is often used for Node crawlers to parse pages. Therefore it is also called Jquery on the server side

Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components' output. You can also manipulate, traverse, and in some ways simulate runtime given the output. Enzyme's API is meant to be intuitive and flexible by mimicking jQuery's API for DOM manipulation and traversal.
Copy the code

To complete the render test, you need the support of the Enzyme Adapter library. The React version is different. The Enzyme Adapter is designed to adapt to different React versions. The mapping between React version and React version is shown as follows:

💻 source code analysis

Enzyme implements a set of apis similar to Jquery. The core code is to build a ReactWrapper class or ShallowWrapper around the React component. The difference is that ShallowWrapper only renders the first layer of the component. Does not render from components, so it is a shallow rendering. The children() API in JQ can obtain all the children of a DOM node. Enzyme does this:

The constructor calls the privateSetNodes method to set the root node

The privateSet function is called

PrivateSet does node assignment via the Object.defineProperty API. Let’s see what the Children API does:

First, we get all the child nodes, which are wrapped in three layers of array operations. We start from the outside:

The flatMap function passes a fn as an argument:

The getNodesInternals function here gets the current root node

The source code is

getNodesInternals() {
    return this[NODES][0]
}
Copy the code

I won’t post pictures

After iterating through the array, we call the WRAP API. Here’s the important step:

The WRAP API guarantees that the root node inherits from the ReactWrapper class

Back above, flatMap expands the root node to strip out false values and output an array

Then the fn passed in by flatMap is

n => childrenOfNodes(...) 
Copy the code

What did you do?

Rendered method of the node node, and rendered an array of child nodes

At this point, let’s look at the Children API:

When all the nodes have been retrieved, the selector is called, with no selector returning all the children, and the API should be able to do this at this point

children('.childClass')
children('.child + .child')...Copy the code

Enzyme also encapsulates many other apis, such as the following:

Simulate (event, mock) : Used to simulate event triggering. Event is the event name and mock is an event object. Instance () : Returns an instance of the test component; Find (selector) : finds a node according to a selector. A selector can be a CSS selector, a constructor of a component, or a display name of a component. At (index) : Returns a rendered object; Get (index) : Returns a React node. To test it, re-render it. Contains (nodeOrNodes) : Specifies whether the current object contains nodes that are the main parameters. The parameter type is react object or object array. Text () : Returns the text content of the current component; HTML () : Returns the HTML code form of the current component; Props () : Returns all properties of the root component; Prop (key) : Returns the specified property of the root component; State () : Returns the state of the root component; SetState (nextState) : Sets the state of the root component; SetProps (nextProps) : Sets the properties of the root component; ChildAt (index) returns the child node whose index is index. Parents (selector) returns the specified parent node.Copy the code

❓ What is a test?

Before we get started with the React Enzyme testing framework, we need to understand the purpose of testing, what tests need to be tested, and what level of automation testing needs to be done.

1. Functional testing 2. Performance testing (including load/stress testing) 3. User interface testing 4. Compatibility test 5. Security test 6. Interface testCopy the code

The purpose of the Enzyme framework is to make the output of the React component predictable and more controllable, and Emzyme tests can be granular to a small React component

Enzyme test API:

1.Shallow Rendering: Shallow (node[, options]) => ShallowWrapper defines a Shallow Rendering that only renders the first layer of a component, not its children, and is much faster to test than the other two.2.Full DOM Rendering: Mount (node[, options]) => ReactWrapper defines a Full Rendering method that fully parses components into a virtual DOM3.Render (node[, options]) Static Rendered Markup: Render (node[, options])Copy the code

The above three apis make React unitary down to the atomic level, allowing us to test the copy of a button, the length of a title, the props of an entire component, and so on

🚗 use

1. Shallow rendering

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import MyComponent from './MyComponent';
import Foo from './Foo';

describe('<MyComponent />', () => {
  it('renders three <Foo /> components', () = > {const wrapper = shallow(<MyComponent />);
    expect(wrapper.find(Foo)).to.have.lengthOf(3);
  });

  it('renders children when passed in', () => {
    const wrapper = shallow((
      <MyComponent>
        <div className="unique" />
      </MyComponent>
    ));
    expect(wrapper.contains(<div className="unique" />)).to.equal(true);
  });
});
Copy the code

2. Render completely (mount)

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

import Foo from './Foo';

describe('<Foo />', () => {
  it('allows us to set props', () = > {const wrapper = mount(<Foo bar="baz" />);
    expect(wrapper.props().bar).to.equal('baz');
    wrapper.setProps({ bar: 'foo' });
    expect(wrapper.props().bar).to.equal('foo');
  });

  it('calls componentDidMount', () => {
    sinon.spy(Foo.prototype, 'componentDidMount');
    const wrapper = mount(<Foo />);
    expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
    Foo.prototype.componentDidMount.restore();
  });
});
Copy the code

3. Render

import React from 'react';
import { expect } from 'chai';
import { render } from 'enzyme';

import Foo from './Foo';

describe('<Foo />', () => {
  it('renders three `.foo-bar`s', () = > {const wrapper = render(<Foo />);
    expect(wrapper.find('.foo-bar')).to.have.lengthOf(3);
  });

  it('renders the title', () => {
    const wrapper = render(<Foo title="unique" />);
    expect(wrapper.text()).to.contain('unique');
  });
});
Copy the code

The instance

Based on the official sample, we started the local project to test. First, we generated a test project through create-react-app and wrote the app.test file

Since the react-test-renderer outputs an Expect API, the Chai Expect API has been renamed

import React from 'react'
import { expect as cExpect } from 'chai'
import Adapter from 'enzyme-adapter-react-16'
import { configure, shallow, mount } from 'enzyme'
import sinon from 'sinon'
import toJson from 'enzyme-to-json'
import UserInfo from './components/UserInfo'
import List from './components/List'
import 'react-test-renderer'

configure({ adapter: new Adapter() })
Copy the code

1. Test nodes and events

describe('I. Test UserInfo component', () => {
  const wrapper = shallow(<UserInfo />)
  it('1. Test button copy ', () => {
    cExpect(wrapper.find('button').text()).to.equal('button')
  })

  it('2. Test node, containing a P tag ', () => {
    cExpect(wrapper.find('p').length).to.equal(1)
  })

  it('3. Mock click ', () => {
    const onBtnClick = sinon.spy();
    const wrapper = mount((
      <UserInfo onBtnClick={onBtnClick} />
    ));
    wrapper.find('button').simulate('click');
    cExpect(onBtnClick).to.have.property('callCount', 1);
  });
})
Copy the code

2. Test props, lifecycle

describe('ii. Test the List component', () => {
  const shallowWrapper = shallow(<List list={['a']} />)
  const wrapper = mount(<List list={['a']} />)

  it('1. List component shallow render ', () => {
    cExpect(shallowWrapper.props().list).to.equal(undefined)
  })

  it('2. List component fully rendered, test Props', () => {
    cExpect(wrapper.props().list.length).to.equal(1)
    wrapper.setProps({list: ['shallow'.'mount'.'render']})
    cExpect(wrapper.props().list.length).to.equal(3)
  })

  it('3. List component execution componentDidMount', () => {
    sinon.spy(List.prototype, 'componentDidMount');
    mount(<List />)
    cExpect(List.prototype.componentDidMount).to.have.property('callCount', 1);
    List.prototype.componentDidMount.restore();
  });
})
Copy the code

3. Snapshot test

describe('III. Testing App Components', () = > {const wrapper = shallow (< List List = {} [1, 2, 3] / >) it ('1. App Component Rendering ', () => {
    expect(toJson(wrapper)).toMatchSnapshot()
  })
})
Copy the code

The test example passes. The snapshot test generates app.test.js. snap

When the component content changes, the snapshot test compares the photo to find the difference:

Here you can clearly see why the component test failed

Common test unit

expect({a:1}).toBe({a:1})// Determine whether two objects are equal
expect(1).not.toBe(2)// The judgment is unequal
expect(n).toBeNull(); // Check whether it is null
expect(n).toBeUndefined(); // Check whether it is undefined
expect(n).toBeDefined(); // The result is opposite to toBeUndefined
expect(n).toBeTruthy(); // Determine the result to be true
expect(n).toBeFalsy(); // The result is false
expect(value).toBeGreaterThan(3); / / is greater than 3
expect(value).toBeGreaterThanOrEqual(3.5); // Greater than or equal to 3.5
expect(value).toBeLessThan(5); / / less than 5
expect(value).toBeLessThanOrEqual(4.5); // The value is less than or equal to 4.5
expect(value).toBeCloseTo(0.3); // Float values are equal
expect('Christoph').toMatch(/stop/); // Regular expression judgment
expect(['one'.'two']).toContain('one'); // Contains an element
expect(toJson(wrapper)).toMatchSnapshot() // Test the snapshot
wrapper.setProps({a: 1}) / / verification setProps
Copy the code

There is more about the front-end testing knowledge we explore together, the code uploaded to Github, you can have a look.