Enzyme, a good helper of Jest, simulated event test is abnormal

Enzyme

Enzyme is Airbnb's open source React test tool library. It provides a concise and powerful API for the secondary encapsulation of the official test tool library ReactTestUtils, with built-in cherio

It implements DOM processing in jQuery style, and the development experience is very friendly. It is very popular in the open source community and has also been recommended by React officials.

API: https://airbnb.io/enzyme/

Three rendering methods

  • Shallow: shallow rendering is the encapsulation of the official Shallow Renderer. Rendering components into virtual DOM objects will only render the first layer, and sub components will not be rendered, which makes the efficiency very high. No DOM environment is required, and the information of components can be accessed by jQuery

  • render: static rendering, which renders the React component into a static HTML string, then parses the string using the cherio library, and returns an instance object of cherio, which can be used to analyze the HTML structure of the component

  • mount: full rendering, which loads component rendering into a real DOM node to test DOM API interaction and component life cycle. jsdom is used to simulate the browser environment

Among the three methods, share and mount can be simulate d interactively because they return DOM objects, while render cannot. Generally, the share method can meet the requirements. If you need to judge sub components, you need to use render. If you need to test the life cycle of components, you need to use the mount method.

common method

  1. simulate(event, mock): simulates an event and is used to trigger the event. Event is the event name and mock is an event object

  2. instance(): returns the instance of the component (not supported by hook)

  3. find(selector): find nodes according to selectors. Selectors can be selectors in CSS, constructors of components, display name s of components, etc

  4. at(index): returns a rendered object

  5. get(index): returns a react node. To test it, you need to re render it

  6. contains(nodeOrNodes): whether the current object contains the key node of the parameter. The parameter type is react object or object array

  7. text(): returns the text content of the current component

  8. html(): returns the HTML code form of the current component

  9. props(): returns all properties of the root component

  10. prop(key): returns the specified attribute of the root component

  11. state(): returns the status of the root component (not supported by hook)

  12. setState(nextState): sets the state of the root component (not supported by hook)

  13. setProps(nextProps): sets the properties of the root component

  14. update(): can only be invoked in the root component, and the root component will be rendered again after props changes.

  15. invoke(invokePropName): call the method passed from the root component to the child component.

Test what

Functional components must be tested in three parts:

  • Props incoming

  • Component branch rendering logic

  • Event invocation and parameter passing

Break whether the child component has xxx attribute or method in the parent component (whether Props is correctly passed in)

// index.js
import React, { Component } from 'react';
import Header from './components/Header'
import UndoList from './components/UndoList'
import './style.css';

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.addUndoItem = this.addUndoItem.bind(this);
    this.deleteItem = this.deleteItem.bind(this);

    this.state = {
      undoList: [],
    }
  }

  addUndoItem(value) {
    this.setState({
      undoList: [...this.state.undoList, {
        status: 'div',
        value,
      }],
    })
  }
  
  deleteItem(index) {
    const newList = [...this.state.undoList];
    newList.splice(index, 1);
    this.setState({
      undoList: newList
    })
  }

  render() {
    const { undoList } = this.state;
    return (
      <div>
        <Header addUndoItem={this.addUndoItem} />
        <UndoList
          list={undoList}
          deleteItem={this.deleteItem}
        />
      </div>
    )
  }
}

export default TodoList;



// index.test.js
it('Header Component exists addUndoItem attribute', () => {
    const wrapper = shallow(<TodoList />);
    const Header = wrapper.find('Header');
    expect(Header.prop('addUndoItem')).toBeTruthy();
  });

  it('addUndoItem Method is called, undoList Data item increase', () => {
    const wrapper = shallow(<TodoList />);
    const { addUndoItem } = wrapper.instance();
    const content = 'study React';
    addUndoItem(content);
    expect(wrapper.state('undoList').length).toBe(1);
    expect(wrapper.state('undoList')[0]).toEqual({
      status: 'div',
      value: content, 
    });
    addUndoItem(content);
    expect(wrapper.state('undoList').length).toBe(2);
  });

The instance method can be used to get the internal member object of the component

// demo 1
// --- index.js
export class Card extends React.Component {
  constructor (props) {
    super(props)

    this.cardType = 'initCard'
  }

  changeCardType (cardType) {
    this.cardType = cardType
  }
  ...
}
  
  
  
  // demo 1
  // --- index.test.js
it('changeCardType', () => {
  let component = shallow(<Card />)
  expect(component.instance().cardType).toBe('initCard')
  component.instance().changeCardType('testCard')
  expect(component.instance().cardType).toBe('testCard')
})
// change event of demo2 input
// --- index.js
<Input value={value} onChange={e => this.handleChange(e)}/>

// demo2 finds the input node, simulates the change event, and passes in the target: {value} value
// index.test.js
it('can save value and cancel', () => {
   const value = 'edit'
   const {wrapper, props} = setup({
      editable: true
   });
   wrapper.find('input').simulate('change', {target: {value}});
   wrapper.setProps({status: 'save'});
   expect(props.onChange).toBeCalledWith(value); // After onChange is called, the parameter is value
})a

If the node to be tested has multiple duplicates, which is not convenient for testing, you can customize multiple attributes to find and test

As shown in the demo below

// demo 3 mock function to simulate click event
// --- index.js
<Button className="picker-operate-component_btn" hoverClass="btn-hover" data-test="cancelTitle onClick={cancelEvent}>
        {cancelTitle}
      </Button>
      <Button className="picker-operate-component_btn f-color" hoverClass="btn-hover"  data-test="confirmTitle onClick={confirmEvent}>
        {confirmTitle}
      </Button>

// demo 3 mock function to simulate click event
// ---index.test.js
  it('chooseBox  click events cancelTitle and confirmTitle', () => {
    const propsCancelEvent = jest.fn();
    const propsConfirmEvent = jest.fn();
    const wrapper = shallow(<ChooseBox cancelEvent={propsCancelEvent} confirmEvent={propsConfirmEvent} />);
    wrapper.find('[data-test="cancelTitle"]').simulate('click');
    expect(propsCancelEvent).toBeCalled(); // onClick method called
    wrapper.find('[data-test="confirmTitle"]').simulate('click');
    expect(propsConfirmEvent).toBeCalled(); // onClick method called
    expect(wrapper).toMatchSnapshot();
  });
// demo 3 
wrapper.find('input').simulate('keyup');

expect(props.onClick).toBeCalled();// onClick method called

// enter event

wrapper.find('input').simulate('keyup',{});

expect(props.onClick).toBeCalled();// onClick method called

Debugging method

debug()

It is inevitable that you need to print out the corresponding objects to view when using the enzyme, but I found that when we directly use conosle The log (wrapper) is printed by the problem. Only one object name will be printed, and the correct knowledge will be printed

console.log(wrapper.find(Button).debug());

Code coverage

Code coverage is a test indicator used to describe whether the code of a test case is executed. The code coverage tool is generally used to calculate the code coverage. Jest integrates the code coverage tool Istanbul.

Four measurement dimensions

  • Line coverage: is each line of the test case executed

  • Function coverage: whether each function of the test case is called

  • Branch coverage: whether each if code block of the test case is executed

  • Statement coverage: whether each statement of the test case is executed

In the four dimensions, if the code is written in a standard way, the line coverage and statement coverage should be the same. There are many situations that trigger branch coverage, mainly the following:

  • ||,&&,?,!

  • if statement

  • switch Statements

Test exception

// Function that throws an exception
const throwNewErrorFun = () => {
	throw new Error(this is a new error)
}

// When asserting that throwNewErrorFun is running, an exception is thrown, and the exception is this is a new error
test('toError', () => {
	expect(throwNewErrorFun).toThrow(this is a new error)
})

Keywords: Jest

Added by jalbey on Fri, 21 Jan 2022 22:58:26 +0200