This article was originally published at: github.com/bigo-fronte… Welcome to follow and reprint.

React unit testing practices

Unit testing refers to the examination and verification of the smallest testable units in software. Unit testing is done by the programmers themselves, and ultimately benefits the programmers themselves. Unit tests are performed to try to prove that the code behaves as expected.

We do unit tests every day, including those of you who think you’ve never written one. You write a function, you log it or you click it on the interface, and that’s also a unit test, and that’s called a temporary unit test. Temporary unit testing software, one is difficult to cover all scenarios, the other is unable to run automatically, greatly increasing the cost of later testing and maintenance. It can be said that sufficient unit testing is the only way to improve software quality and reduce development cost.

Here’s my own React unit testing practice to get you started. The advantages and disadvantages of the following as well as the practice methods, are personal subjective color, if there are different opinions, hope not hesitate to comment ~

Advantages of unit testing

It can use lower cost to verify the stability of the code, and basically ensure that the target code has been running in accordance with the initial expectation. At the same time, it can access continuous integration, carry out low-cost reuse, find problems in the first time, and reduce maintenance costs.

  // Verify that the keepInTarget helper method always returns the intersection of ADNS and targets
  // If you test this functionality on the interface, one operation will take enough time to write the following test cases
  it.each([
    // eslint-disable-next-line no-sparse-arrays[[],undefined[], [[], [], [], [['abc'[], [], [], [[], []'abc'], []],
    [['abc'], ['abc'], ['abc']],
    [['abc'.'bcd'], ['abc'.'cde'], ['abc']]]) ('keepInTarget %#'.(adns, targets, expectValue) = > {
    expect(keepInTarget(adns as any, targets)).toEqual(expectValue);
  });
Copy the code

It helps developers think about how to organize their code in a different way and make it more structured. Have you ever seen a class with more than 1,000 lines of code, a method with global state and internal properties, and a method that takes more than three screens to see? Mainly reflected in: can promote thinking method, function boundary, rather than regardless of all in a method; The ability to subliminally write pure functions;

Can make your colleagues think you have something 🐶,

When do you start writing unit tests

It is recommended that you start writing unit tests now. Unit tests should be non-intrusive and should not affect the functionality of your code. This means that you can write as many tests as you want, adding tests when you have time to write them, and not when you have time to write them.

The reason is the second point above, as long as you plan to write unit tests, even if you don’t end up writing unit tests for whatever reason, your code will be different. Believe me, I read more will not cheat you 🐶,

Define test scope

Before you start writing unit tests, you first need to know what the current test needs to test.

Take components as an example. Typically a component consists of: UI rendering, tool methods, internal logic handling, third-party dependencies, and self-written child components. So with all these things you need to test and you don’t need to test, you need to think about that before you write the test.

It is recommended to add tests in a prioritized order: critical logic code, complex logic code, tool methods, and other code. Gradually increase test coverage on your own time.

In practice, we can try to transform blind interface self-testing into unit testing code in the self-testing link, so that we can even get better self-testing effect in less time.

Selection of the framework

There are many libraries of test components, so I used the most popular: jest + enzyme (some examples use @testing-library/*), and @testing-library/react-hooks to test the hooks

Jest, as a testing framework, has a set of system that a testing framework should have, rich assertion library, most API and the old testing framework such as Jasmine, Mocha, such as commonly used Expect, Test (IT), toBe, etc., are very easy to use. The interior is also based on jasmine, which is encapsulated on top of it. However, because of the Snapshot feature, it is very suitable for testing react projects.

Enzyme provides several ways to render the React component into a real DOM, and provides a jquery-like API to obtain the DOM. A SIMULATE function is provided to simulate event triggering. Provides interfaces that let us get the state and props of the component and operate on them. The act-test-renderer API is very unfriendly, but the enzyme API is the same as jquery

Testing-library /react-hooks is a library specifically designed to test React Hooks. We know that a hook is a function, but we can’t test it in the same way that we would test a normal function. Because running them involves a lot of React runtime stuff, many people write testComponents to run them to test their hooks. This approach is inconvenient and difficult to cover all scenarios.

Framework recommendations for use:

  1. If you just want to test the functional functions, introduce JEST and its associated dependencies
  2. If you also want to test the React component, add and introduce enzymes and their associated dependencies
  3. If you also want to test hooks, add @testing-library/react-hooks and their related dependencies
  4. After all, I don’t know when it will be used. May I have time to adjust the configuration parameters

For details, see create-react-app

Common operations

Debug test code

Jestjs. IO /docs/troubl…

Test code is also code, often test code errors lead to error, log output again and again is inefficient, troubleshooting problems is also a test of imagination, so it is necessary to learn the debugging of test code. The principle of debugging the test code is to use the –inspect-brk parameter passed during node execution to debug the test code. Since the test code is run based on the Node environment, there are the following debugging methods: Add the –inspect-brk parameter directly when running the test command, using the editor’s debug interface in conjunction with Chrome.

Run the test script directly with the –inspect-brk parameter

Run (MAC) node –inspect-brk node_modules/.bin/jest –runInBand from the project root, then open Chrome, Enter Chrome ://inspect to Open Dedicated DevTools for Node. This will Open a debug panel that automatically breaks a file at the beginning of the file.

Note: the –runInBand cli option makes sure Jest runs the test in the same process rather than spawning processes for individual tests. Normally Jest parallelizes test runs across processes but it is hard to debug many processes at the same time.

# --inspect-brk enables node debug mode
# --runInBand
# node --inspect-brk node_modules/.bin/jest --runInBand [any other arguments here]
# or on Windows
# node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand [any other arguments here]
Copy the code

Using the editor’s debug interface (recommended)

Here is an example of vscode operation steps (the following operation buttons are the default window interface positions, if you have customized before, please use the actual menu after customization)

  1. Click on run and debug in the side menu bar of vscode (there is also a bug icon in the menu)
  2. Click Add Configuration at the top of the small side window that appears
  3. A file namedlaunch.json, which may or may not already have configuration items
  4. Configurations add Debugging JEST-related configurations in configurations by referring to the jEST official documentation Debugging in VS Code
  5. Once the configuration is complete, select the configuration (usually Debug Jest Tests) at the top of the Debug window and click the Run button (a triangle symbol) in front of the configuration.
// A debug configuration is currently available
{
  "version": "0.2.0"."configurations": [{"name": "Debug Jest Tests"."type": "node"."request": "launch"."runtimeArgs": [
        "--inspect-brk"."${workspaceRoot}/node_modules/.bin/jest"."--runInBand"]."console": "integratedTerminal"."internalConsoleOptions": "neverOpen"."port": 9229}}]Copy the code

Record the snapshot

If you don’t have any test cases in your project, using snapshot tests is the fastest way to implement a basic assurance.

At a Christmas egg event, if the project had a snapshot test, there might not have been so many bad workers.

Advantages and disadvantages of snapshot testing:

  • advantages
    • Low investment: A snapshot test usually takes only 2 lines of test code and doesn’t require much logic
    • The payoff is high: Snapshot tests cover the code with a high degree of coverage and design a snapshot that covers all UI changes of the component under test; The ability to listen for any subtle changes to a given UI
  • disadvantages
    • Not completely reliable: Snapshot testing only ensures that the rendered structure has not changed, and does not record the logical changes well
      • Relying too much on snapshot tests can cause logic errors to be ignored
      • It is recommended to add targeted generic tests for key logic
    • Frequent failures: Any code that causes the UI to change will cause the snapshot test to fail and then need to manually update the snapshot
      • Easy to produce the Wolf effect, thus subjectively ignore the failure, resulting in the loss of the meaning of the test
      • For this reason, there are even teams that prohibit write snapshot tests
      • You are advised to specify the test scope for a snapshot test. If a snapshot test contains too many components, it is more likely to fail

I think the benefits of snapshot testing outweigh the disadvantages. I recommend taking snapshots of the base scenarios and writing generic tests for the key logic

  • Snapshot testing is cost-effective and takes a negligible amount of time, but adds a basic level of assurance to the project
  • Even if you write a lot of logical tests, you still need to supplement them with snapshot tests, because it would be too expensive to write test cases for every UI
  • There aren’t many test cases in the beginning, and there aren’t too many that fail to be ignored
  • While starting out writing tests can be tricky and other logical tests can take a lot of time, snapshot tests can be written quickly

Shallow render record snapshots are strongly recommended, as full-rendered snapshots are too large. Here’s a practical example: There is a form with 41 ANTD form entries. After recording 12 full snapshots, the snapshot record file size reaches 13MB, that is to say, the snapshot size exceeds 1MB and is only 522KB after the enzyme shallow rendering is used. The average snapshot is 40 KB, a significant reduction in size

Recording a snapshot:

  1. use@testing-library/reactIn therenderMethod, which deeply walks through the dependencies, rendering the complete DOM structure if you haven’t mocked the specified component.
    1. Because it’s a deep traversal dependency, you can get a lot of weird errors, usually just mock them out or follow the prompts to solve the problem. Because some components have global dependency properties, testing without them will result in an error.
    2. Generally speaking, if the error part has nothing to do with the test, you can mock it out directly. If it is important, it is recommended to complete the corresponding dependency.
    3. This method relies on a “class browser” environment, which is typically usedjsdomSimulation.
    4. Since the test cases can interact with each other because they are completely rendered in a browser-like environment, it is recommended that each test case be cleaned up after execution
      1. Older versions are automatically cleaned by default
/ / introduction of the react
import React from 'react';
// Introduce test methods
import { render } from '@testing-library/react';
// The component under test
import App from 'pages/Media/App';

// The test component must be wrapped with the Router component because it uses the Link component
// If you don't want to introduce the Router component, mock the Link component as a div or a tag
import { MemoryRouter } from 'react-router-dom';

describe('snapshot'.() = > {
  it('library render'.() = > {
    / / rendering
    const { container } = render(
      <MemoryRouter>
        <App />
      </MemoryRouter>
    );
    // Snapshot record
    expect(container).toMatchSnapshot();
  });
});
Copy the code
  1. useenzymeIn theshallow \ mount \ renderOne of three methods, all of which have different rendering modes
    1. Use the Enzyme test React (Native) component to test the technology radar
    2. shallow:
      1. Shallow rendering renders only the structure of the component under test; child and dependent components are not rendered.
      2. The child component does not affect the test component
      3. You can usesimulateMethod simulation interaction
      4. This method is the fastest
    3. mount:
      1. Full rendering also requires a “class browser” environment to run.
      2. You can usesimulateMethod simulation interaction
      3. Each test case is recommended to be cleaned up after execution, since it is actually rendered into the environment
    4. render:
      1. Render static HTML strings in full, but with no actual logic involved
// shallow 
import { shallow } from 'enzyme'

describe('Enzyme Shallow'.() = > {
  it('App should have three <Todo /> components'.() = > {
    const app = shallow(<App />)
    expect(app.find('Todo')).to.have.length(3)})}// mount
import { mount } from 'enzyme'

describe('Enzyme Mount'.() = > {
  it('should delete Todo when click button'.() = > {
    const app = mount(<App />)
    const todoLength = app.find('li').length
    app.find('button.delete').at(0).simulate('click')
    expect(app.find('li').length).to.equal(todoLength - 1)})})// render
import { render } from 'enzyme'

describe('Enzyme Render'.() = > {
  it('Todo item should not have todo-done class'.() = > {
    const app = render(<App />)
    expect(app.find('.todo-done').length).to.equal(0)
    expect(app.contains(<div className="todo" />)).to.equal(true)})})Copy the code

Triggering event

In some cases, you need to emit an event to test whether the response operation of the page is working properly. FireEvent or Simulate can be used to trigger an event

Sometimes it’s hard to know exactly how to trigger an event from a third-party component. In this case, you can find the source code of the third-party component, find their test cases, and see how they do it themselves.

// The antD Search component enter event needs to be triggered
// Find the antD test case, find the input directly, then trigger the keyDown event, corresponding to keyCode 13
  // it('should trigger onSearch when press enter', () => {
  // const onSearch = jest.fn();
  // const wrapper = mount(
      );
  // wrapper.find('input').simulate('keydown', { key: 'Enter', keyCode: 13 });
  // expect(onSearch).toHaveBeenCalledTimes(1);
  // expect(onSearch).toHaveBeenCalledWith(
  // 'search text',
  // expect.objectContaining({
  // type: 'keydown',
  // preventDefault: expect.any(Function),
  / /}),
  / /);
  // });

  // Write your own test cases for the @testing-library/react framework version
  // Search the input field and press Enter
  const searcher = container.querySelector('.ant-input-search.table-head-search .ant-input')! ; fireEvent.keyDown(searcher, {key: 'Enter'.code: 'Enter'.keyCode: 13 });
Copy the code

Common mock methods

Mock a method in a dependency library

Jest. RequireActual (moduleName) Jest. RequireActual (moduleName) Jest. RequireActual (moduleName) Jest.

// Only mock the run method returned from useAntdTable calls provided by ahooks
jest.mock('ahooks'.() = > ({
  ...jest.requireActual('ahooks'),
  useAntdTable: (. args) = > {
    const res = jest.requireActual('ahooks').useAntdTable(... args);return { ...res, run: jest.fn() }; }}));Copy the code

Mock Considerations

  1. jest.mockWill be promoted to the top of the file, that is, will run before the import dependency
    1. So importing A from XXX and then using A in the jest. Mock callback is not an out-of-scope variables
    2. One solution to the above problem is the mockImplementation that someone answered in the above problem
    3. Another option, listed in the previous scenario problem, is to use requireActual
  2. Similarly, it is not allowed to mock outside variables in jest
    1. Error: Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed withmock (case insensitive) are permitted.
    2. According to the error message, need tomock(case insensitive) as a prefix.
    3. And since this variable needs to be used deferred, using it directly in the jest. Mock callback will report “variable undefined” and needs to be used in a method
// You must start with a mock to tell Jest that this is a mock variable and needs to be deferred
let mocksubmit;

jest.mock('ahooks'.() = > {
  constorigin = { ... jest.requireActual('ahooks')};// If written here, mockSubmit is undefined
  // Because MockSubmit uses the mock prefix, it is marked as delayed use
  // So the above let MockSubmit statement has not yet been run in the function runtime environment
  // const spy = { submit: jest.fn() };
  // // Note the timing of assignment, mock variables
  // mocksubmit = jest.spyOn(spy, 'submit');

  return {
    ...origin,
    useAntdTable: (. args) = > {
      const spy = { submit: jest.fn() };
      // The assignment was successful because it was run at the time useAntdTable was called
      // After the App renders, let MockSubmit is already running
      mocksubmit = jest.spyOn(spy, 'submit');
      consto = origin.useAntdTable(... args); o.search = { ... origin.search,submit: jest.fn(mocksubmit),
      };
      returno; }}; });Copy the code

all jest.mock will be hoisted to the top of actual code block at compile time, which in this case is the top of the file.

Antd 3.X version of the form property mock

3. In the X version of ANTD, form.create wrapped components will have their own this.props. Form property, but this makes testing difficult. If we use the enzyme mount method to render, and just test the logic, it’s not a problem. Because we can directly take the packaged components for full rendering, basically can meet the test requirements. However, if this rendering is not good enough to record a snapshot, the output snapshot is too large and therefore needs to be shallow for rendering. However, using shallow to render and take a snapshot is problematic, and if you render with a wrapped component, the snapshot does not get the render result of the form item, and such a snapshot is not useful. Shallow rendering the original component shallow rendering the original component directly caused a TypeError: Cannot destructure property getFieldDecorator of ‘undefined’ or ‘null’.

// Shallow wrapped component retrieves only one form, and the form-item is not rendered (shallow wrapped component)
<SlotOperation
  form={
    Object {
      "getFieldDecorator": [Function]."getFieldError": [Function]."getFieldInstance": [Function]."getFieldProps": [Function]."getFieldValue": [Function]."getFieldsError": [Function]."getFieldsValue": [Function]."isFieldTouched": [Function]."isFieldValidating": [Function]."isFieldsTouched": [Function]."isFieldsValidating": [Function]."isSubmitting": [Function]."resetFields": [Function]."setFields": [Function]."setFieldsInitialValue": [Function]."setFieldsValue": [Function]."submit": [Function]."validateFields": [Function]."validateFieldsAndScroll": [Function],}} / >// If you attempt to shallow the original component directly, you will receive an error
// TypeError: Cannot destructure property `getFieldDecorator` of 'undefined' or 'null'.

// 152 | public render() {
// 153 | const { permission, form, sspType } = this.props;
// > 154 | const { getFieldDecorator, getFieldsValue } = form;
/ / | ^
// 155 | const formData = getFieldsValue();
// 156 | const {


Copy the code

At this point the hole has been dug, I will be responsible to fill in, here is a hack method (not sure if there is a better way, if anyone knows please contact me). First of all, our core requirement is that form items should be recorded when taking snapshots, and the snapshot size should not be too large. This requires: first render using shallow; Using shallow then requires that only the original form component be rendered directly. The question is: What if the mock form component requires a form attribute? The idea here is to wrap a mock component with form.create, render the wrapped component, and assign the Form to an external variable that can then be used by the component you want to test.

  / /...

  // Render the fake component to fetch the form
  // Use methods that return different forms each time to prevent interference between different test cases
  export function getForm() {
    let form: WrappedFormUtils<any>;
    const Fake = Form.create()((props: any) = > {
      form = props.form;
      return <div />;
    });
    const container = document.createElement('div');
    ReactDOM.render(<Fake />, container);
    ReactDOM.unmountComponentAtNode(container);

    // @ts-ignore
    return form;
  }

  / /...

  // Pass the above form to the component under test. No more errors are reported, and the rendering results are normal
 const shallowRes = shallow(<Test form={getForm()} />);

 expect(toJson(shallowRes)).toMatchSnapshot({}, 'I took a snapshot of the form entry');
Copy the code

Some unit test sample code

Test tool functions, constants

// pattern. Ts Specifies a common regular expression
// No Spaces before or after
export const NO_BLANK_AROUND = / ^ (? ! \s)(? ! .*\s$)/;
export const genWordLimits = (min = 5, max = 37) = > new RegExp(` ^. {${min}.${max}} $`);

// pattern.spec.ts tests whether the regular expression is correct
import { NO_BLANK_AROUND, genFixed, genWordLimits, genIntLimits } from '.. /pattern';
describe('Test re'.() = > {
  it('NO_BLANK_AROUND'.() = > {
    expect(NO_BLANK_AROUND.test(' ')).toBe(true);
    // eslint-disable-next-line max-len
    // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#firefox%E7%89%B9%E6%AE%8A%E 6%B3%A8%E6%84%8F
    // Before Firefox 8, test() was implemented incorrectly;
    // When called without arguments, it matches the previous input value (the regexp. input property) instead of the string "undefined".
    // This has been fixed; /undefined/.test() now returns true correctly, not an error.
    // Test () equals test('undefined')
    expect(NO_BLANK_AROUND.test('undefined')).toBe(true);
    expect(NO_BLANK_AROUND.test('abc')).toBe(true);
    expect(NO_BLANK_AROUND.test(' abcde')).toBe(false);
    expect(NO_BLANK_AROUND.test('ab ')).toBe(false);
    expect(NO_BLANK_AROUND.test(' abcdef ')).toBe(false);
  });
  it('genWordLimits'.() = > {
    const limits = genWordLimits(3.5);
    expect(limits.test(' ')).toBe(false);
    expect(limits.test('abc')).toBe(true);
    expect(limits.test('abcde')).toBe(true);
    expect(limits.test('ab')).toBe(false);
    expect(limits.test('abcdef')).toBe(false);
    // @ts-ignore
    expect(limits.test()).toBe(false);
  });
});
Copy the code

A snapshot of the test

// index.tsx ListCtr form component.// index.spec. TSX tests the ListCtr form component
import React from 'react';
import { shallow } from 'enzyme';
// @ts-ignore
import { getForm } from 'src/__tests__/utils/form';
import { ListCtrl } from '.. /index';
import toJson from 'enzyme-to-json';

describe('snapshot'.() = > {
  it('shallow render'.() = > {
    const shallowRes = shallow(<ListCtrl form={getForm()} />);
    expect(toJson(shallowRes)).toMatchSnapshot({}, 'base');
  });
});

Copy the code

Test UI

import React from 'react';
import { render, shallow, mount } from 'enzyme';
import { SlotOperation } from '.. /SlotOperation';
import { Checkbox, Form } from 'antd';
import { genTabel } from '__tests__/utils/common';
import { baseComponentProps, app } from '__tests__/pages/Media/App/slotOperation.mock';
import { BrowserRouter } from 'react-router-dom';

/ /... Some mock operations

// Check that the page rendered items are correct
describe('items check'.() = > {
  // sspType 1 create
  constbase = { ... baseComponentProps };const RenderForm = Form.create()(SlotOperation);
  const mountRes = mount(
    <BrowserRouter>
      <RenderForm {.(base as any)} / >
    </BrowserRouter>
  );

  const target = mountRes.find(SlotOperation);
  target.setState({ adTypes: {}, app, action: 'create' });

  describe('sspType 1'.() = > {
    // Base Settings
    it.each(
      genTabel(
        [
          'AD space Name'.// 'systemSource',
          ['location_testkey'.false].'AD Space Type'['Template ads'.false],
          ['Template AD ID'.false],
          ['Banner size'.false],
          ['Topview Style associated AD space '.false],
          ['Banner auto-refresh'.false],
          ['Interval time'.false],
          ['Is it allowed to quit the motivational video?'.false],
          ['Automatic video replay'.false],
          ['Video Direction'.false],
          ['Optimal resolution'.false],
          ['Video advertising Sound'.false],
          ['Screen countdown Duration'.false]],true(a))'%s sholud exists %s'.(label, exists) = > {
      expect(mountRes.find(`FormItem[label="${label}"] `).exists()).toBe(exists);
    });
    it('SlotFrequency should exists true'.() = > {
      expect(mountRes.find(`SlotFrequency`).exists()).toBe(true);
    });
    it('Traffic from old US system should exist true'.() = > {
      expect(mountRes.containsMatchingElement(<Checkbox>The traffic comes from the old American system</Checkbox>)).toBe(true);
    });
  });
});

Copy the code

Test component logic

// tplconfig. TSX TplConfig component.// tplconfig.spec. TSX tests whether the interaction logic of the TplConfig component is as expected
import React from 'react';
import { render, shallow, mount } from 'enzyme';
import TplConfig from '.. /TplConfig';
import Form from 'antd/lib/form';
import { Checkbox } from 'antd';

jest.mock('pages/Media/TplManage/util/request'.() = > Promise.resolve({ list: []})); beforeAll(() = > {
  console.warn = jest.fn();
  console.error = jest.fn();
});

describe('TplConfig'.() = > {
  const base = {
    formItemLayout: {},
    appId: ['1'].data: {},
    adTypes: {},
    noAdd: false,}as any;
  const FormWrapper = Form.create()(TplConfig);
  const mountRes = mount(<FormWrapper {. base} / >);
  const form = mountRes.find(TplConfig).props().form;

  beforeEach(() = > {
    mountRes.setProps({ adTypes: {}}); mountRes.update(); });/ / native
  it('templateAdLock native'.() = > {
    mountRes.setProps({ adTypes: { 1: ['1']}}); mountRes.update();const tpl = mountRes.find(FormItem[label=" FormItem "]);
    expect(tpl.exists()).toBe(true);

    expect(tpl.containsMatchingElement(<Checkbox>Enabling native Templates</Checkbox>)).toBe(true);
    expect(tpl.containsMatchingElement(<Checkbox disabled={true}>Enable algorithmic bidding (valid for BigODSP)</Checkbox>)).toBe(
      true
    );

    expect(form.getFieldValue('templateAdLock')).toBe(0);
    tpl.find('CheckboxGroup .ant-checkbox-input').at(0).simulate('change');
    expect(form.getFieldValue('templateAdLock')).toBe(1);
    expect(mountRes.find('FormItem[label=" Configure native template "]').exists()).toBe(true);
  });
});

Copy the code

Test the hooks

// useadnlist.hook. Ts useAdnList file.// useadnList.hook.spec. ts Tests whether useAdnList is normal
import { renderHook } from '@testing-library/react-hooks';
import { AdTypeEnum } from 'constants/app';
import { useAdnList, ADNConfs } from '.. /useAdnList.hook';

describe('useAdnList.hook'.() = > {
  it('should be defined'.() = > {
    expect(useAdnList).toBeDefined();
  });

  it.each([
    [[], {}, []],
    [[], { 1: ['1'] }, []],
    [[AdTypeEnum.NATIVE], { 1: ['1'] }, [...ADNConfs]],
    [[AdTypeEnum.NATIVE], { 1: ['1'].2: [] }, [...ADNConfs]],
    [[AdTypeEnum.NATIVE], { 1: ['1'].3: [] }, [...ADNConfs]],
    [[AdTypeEnum.REWARD_VIDEO], { 4: [{} [],title: 'bigoad'.value: 'bigoad' }]],
    [[AdTypeEnum.REWARD_VIDEO], { 1: ['1'].4: [{} [],title: 'bigoad'.value: 'bigoad' }]],
    [
      [AdTypeEnum.INTERSTITIAL],
      { 3: []}, [{title: 'bigoad'.value: 'bigoad' },
        { title: 'bigobrand'.value: 'bigobrand' },
        { title: 'bigobrand_cpm'.value: 'bigobrand_cpm' },
      ],
    ],
    [
      [AdTypeEnum.INTERSTITIAL],
      { 3: [].4: []}, [{title: 'bigoad'.value: 'bigoad' },
        { title: 'bigobrand'.value: 'bigobrand' },
        { title: 'bigobrand_cpm'.value: 'bigobrand_cpm' },
      ],
    ],
    [[AdTypeEnum.INTERSTITIAL], { 1: ['1'].3: [{} [],title: 'bigoad'.value: 'bigoad'}]]]) ('mounted value %#'.(adTypeList, adTypes, expectValue) = > {
    const hook = renderHook(() = > useAdnList(adTypeList as any, adTypes, jest.fn));
    expect(hook.result.current).toEqual(expectValue);
  });

  it('update adTypes'.() = > {
    let adTypeList = [AdTypeEnum.INTERSTITIAL];
    let adTypes = { 4: []}as any;
    const spy = jest.fn();
    const hook = renderHook(() = > useAdnList(adTypeList as any, adTypes, spy));
    const expectValue1 = ['bigoad'.'bigobrand'.'bigobrand_cpm'].map((v) = > ({ title: v, value: v }));
    expect(hook.result.current).toEqual(expectValue1);

    adTypes = {};
    hook.rerender();
    expect(hook.result.current).toEqual(expectValue1);

    adTypes = { 1: ['1'].4: []}; hook.rerender(); expect(hook.result.current).toEqual([{title: 'bigoad'.value: 'bigoad' }]);

    adTypeList = [AdTypeEnum.NATIVE];
    hook.rerender();
    expect(hook.result.current).toEqual([...ADNConfs]);

    adTypes = { 4: []}; adTypeList = [AdTypeEnum.INTERSTITIAL]; hook.rerender(); expect(hook.result.current).toEqual(expectValue1); }); });Copy the code

Record some problems encountered and resolved

react-i18next:: You will need to pass in an i18next instance by using initReactI18next

www.tangshuang.net/3824.html react.i18next.com/misc/testin… Levelup.gitconnected.com/internation…

Reason: The tested component uses I18N, but the current test environment does not initialize the i18N configuration. Solve: 1. Directly introduce the i18N initialization configuration of the project (there will be problems in suspense); Mock related multilingual attributes (recommended)

jest.mock('react-i18next', () :any= > ({
  useTranslation: (a) :any= > ({
    t: (key: string): string= > key,
  }),
}));
Copy the code

Login suspended while rendering, but no fallback UI was specified.

Stackoverflow.com/questions/5…

A possible reason for this error is that the test component uses const {t} = useTranslation(); In Suspense, this hook requires components to be wrapped in Suspense components:

  1. Example Modify i18N configurations
```js i18n .use(XHR) .use(LanguageDetector) .init({ react: { useSuspense: false // <---- this will do the magic } }); ` ` `Copy the code
  1. Wrap the component under testSuspense
```js
<Suspense fallback={<div>Loading... </div>}>
      <App />
</Suspense>
```
Copy the code

Suspense only renders fallback

Stackoverflow.com/questions/5…

Reason: Components in Suspense packages have a loading state and only render fallbacks at first

Invariant failed: You should not use <Link> outside a <Router>

Cause: The tested component uses the Link component internally, so it needs to be wrapped with the Router component: BrowserRouter is used as the wrapped component

import { BrowserRouter } from 'react-router-dom';

<BrowserRouter>
  <TestApp />
</BrowserRouter>
Copy the code

TypeError: window.matchMedia is not a function

Stackoverflow.com/questions/3…

window.matchMedia = (query) = > ({
  matches: false.media: query,
  onchange: null.addListener: jest.fn(), // Deprecated
  removeListener: jest.fn(), // Deprecated
  addEventListener: jest.fn(),
  removeEventListener: jest.fn(),
  dispatchEvent: jest.fn(),
});
Copy the code

Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)

My.oschina.net/u/2263272/b… Ask.csdn.net/questions/6…

Install jest-canvas-mock, which can be introduced in the current test file or in setuptests.js

TypeError: Cannot set property ‘font’ of undefined

Jest-canvas-mock will be removed and canvas will be installed again to solve the problem. Make sure that the previous dependency is removed cleanly.

TypeError: Function.prototype.name sham getter called on non-function

This error is generated only when test coverage is generated, i.e. the –coverage parameter is used

There are random numbers in the component, causing the snapshot tests to always fail

Cause: Random number is different every time a snapshot is generated. For example, mock math.random if a random number is generated using math.random ().

More common is the return of new Date(), which you can mock using the MockDate library

import MockDate from 'mockdate';

MockDate.set(-639129600000);
Copy the code

If the random number is in an external dependency, you can find the source code of the dependency to see what the random method is, or use shallow rendering to try to circumvent the rendering of that random number.

If the mock doesn’t work, you need to check that the timing of the mock is after the code runs, and if so, you need to find a way to move the timing of the mock forward.

No shallow rendering methods

Github.com/enzymejs/en…

Reason: @testing-library/react does not provide a shallow rendering method. If shallow rendering is required, use enzyme. Currently, it is recommended to use enzymes directly.

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer’s output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See Reactjs.org/link/uselay… for common fixes.

Stackoverflow.com/questions/5…

Use the enzyme render method to render the HTML structure, useLayoutEffect will indicate that the render was successful, but it is recommended to handle this error. Mock the whole react or mock only the react hook.

// mock the whole react
/ / recommend
jest.mock('react'.() = > ({
  ...jest.requireActual('react'),
  useLayoutEffect: jest.requireActual('react').useEffect,
}));

// The simple mock useLayoutEffect method
// Not recommended, need to be in the setuptest.js file to take effect, not flexible

import React from "react" 
React.useLayoutEffect = React.useEffect 

Copy the code

Warning: An update to List inside a test was not wrapped in act(…)

  1. Generally speaking, yes@testing-library/reactThe framework does not need to manually wrap the ACT methodusing-act-and-wrapperupdate
  2. So if you are using the enzyme framework and this error occurs, you can follow the documentation to try to wrap your state update operation with act
  3. And then if you are using@testing-library/reactIf the problem is solved, then congratulations you can skip the following steps
  4. If, like me, you cannot resolve the error by adding act and async await, you can try using waitForElement
  5. Of course, waitForElement is obsolete due to versioning problems. But the idea is the same: after a state update occurs, look for the result of that state update,

If the update is found, the update is finished. 2. The latest API for [email protected] is to use the findBy* selector to find elements, a solution that has already been answered in the q&A above. 3. Here I will record the reason for this error again. After all, generally speaking, this error will not occur. First, an event is raised in the test. 2. This event does not directly update the status, but instead fires another event. Then the second event triggers a status update, which causes the default ACT to fail. 4. Moreover, no matter how I manually add the ACT, it still doesn’t work. 5. Because I can’t directly wrap the ACT in the event where the state changes

How do the results returned by higher-order components test the original component

  1. Suppose you have a component that needs to be tested, and you can’t get the original anywaySlotOperationComponent, because this component uses a lower version of ANTDForm, must be handled using form.create.

You can use the enzyme find method to find the component you want to test based on the component name.

// The component under test must be processed by higher-order components
export default connect(mapStateToProps)(
  Form.create<IProps>({
    name: 'slot_operation',
  })((props: IProps) = > {
    if(! props.appId || ! props.adSpaceId) { history.replace('/media/app');
      return null;
    }
    return <SlotOperation {. props} / >; }));// Test pseudocode
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import SlotOperation from 'pages/Media/App/SlotOperation';
const mountRes = mount(
  <BrowserRouter>
    <SlotOperation {.(base as any)} / >
  </BrowserRouter>
);


const slot = mountRes.find('SlotOperation');

Copy the code
  1. If you can bypass that higher-order component, mock that higher-order component and return the original component as is.
  2. Export the original components directly for testing, because unit tests should only test one object at a time, not joint testing with higher-order components. If you believe that the higher-level component really needs to be tested, you can add separate tests for that higher-level component.

Welcome everyone to leave a message to discuss, wish smooth work, happy life!

I’m bigO front. See you next time.