2
头图

Preface

Unit testing is used to test a small function in the program, such as a function, a class. It can significantly improve the code quality of the project, reduce the frequency of bugs, and also help maintain the code.

Having said that, the vast majority of people are unwilling to write a single test. . But it doesn’t matter, it’s not a bad thing to learn more.

The background will not be introduced much, it is mainly practice.

text

In this project, the framework I used is Jest. The usage is relatively simple, here is a brief introduction:

What is a unit test?

Unit Testing, also known as module testing, is a test for correctness verification of program modules (the smallest unit of software design).

TDD: Test Driven Development

Write test cases first, and complete the functions under the guidance of the test cases. After the test cases are written and all passed the tests, the corresponding functions are completed. For example, functions and component libraries. But usually when the code changes, the test cases have to be adjusted accordingly.

BDD: Behavior Driven Development

The test case simulates the user's operation behavior, usually after the completion of the business code development, the user's operation is used as a guide to write the test code. When the test case runs through, it can be considered that the overall process of the system has been smooth. The BDD model is suitable for normal business code development, because business requirements may change frequently, but the operation process may not change. When the business code changes, you can use the original test cases to continue to run the code, saving development time.
TDD is responsible for the testing of method classes and independent components. BDD is responsible for the testing of the overall business module.

need a single test?

status quo:
Lack of awareness: There is no single test awareness, and many codes do not have a single test
Lack of design: module lacks design, mutual coupling, difficulty in writing a single test
Test difficulty: upgrade public API, manual test verification is difficult
test is not comprehensive: part of the code has many branches, and manual testing cannot cover all branches

measure the perfection of a single test?

The measurement of the completeness of a single test is measured by the coverage rate

image.png

single test

Capacity building: A developer with development experience will basically write unit tests. Even if not, it can be achieved quickly through training. From the perspective of the learning curve, unit testing is easy to learn.
Improve efficiency: The problem can be found early through mock data, and the earlier the bug is found, the less waste will be caused.
Pursuit of excellence: Unit testing can be used as a design tool, it helps developers to think about the design of code structure, making the code more conducive to testing.
test is more comprehensive: it can cover situations that the QA test cannot cover, such as various if branches and exception handling.
more confident: When upgrading the public API, if all code single tests that rely on this API can pass, then we are more confident in this code upgrade.

This is the end of the concept introduction of

1. Assert

The most used assertion library in Jest, we can use it to test whether the output of the target function is consistent with expectations.

test("测试 2 + 2 = 4",()=>{
  expect(sum(2,2)).toBe(4)
})
test("测试函数返回对象为 {name: 'zhi'}",()=>{
  expect(getName()).toEqual({name:"zhi"})
})

More to view official document .

2. Asynchronous code test

There are usually two ways of writing:

  • Callback
  • Function returns promise

When testing asynchronous code, the returned data is usually uncertain, so we only need to test whether the asynchronous code returns data normally.

For the callback function, if you test like a synchronous function, there is no way to get the correct assertion result

export const funcA = (callback: (data: number) => void): void => {
  setTimeout(() => {
    callback(1);
  }, 1000);
};
​
test('funcA', () => {
  funcA((data) => expect(data).toEqual(2));
});
​

Even so, our single test can pass.

This is because in jest finished running funcA directly after the end, do not wait setTimeout callback, naturally there is no execution expect assertion. The correct approach is to pass in a done parameter:

test('funcA', (done) => {
  funcA((data) => {
    expect(data).toEqual(2);
    done();
  });
});

After the callback is executed, explicitly tell jest that the asynchronous function is executed, and jest will wait until done() executed before ending, so that the expected result can be obtained.

For Promise, only needs test end use cases, the Promise returned to.

export const funcB = (): Promise<number> => {
  return new Promise<number>((resolve) => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });
};
​
test('funcB', () => {
  return funcB().then((data) =>   expect(data).toEqual(1));
});
// 也可以使用await
test('funcB', async () => {
  const data = await funcB();
  expect(data).toEqual(1);
});

For a promise to throw an exception, you need to use the .catch method. There is a caveat here. Add expect.assertions to verify that a certain number of assertions are called. Otherwise, a promise in a fulfilled state will not fail the test case.

test('the fetch fails with an error', () => {
  expect.assertionsassertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});

3. Mock

If we need to test whether the callback function is executed, we can use mock. Here I would like to introduce the usage of mock based on practice.

Scenario 1: A react hooks uses the useLocation of react-router. We want to simulate whether this is called

import { renderHook, act } from '@testing-library/react-hooks';

let mockLocation = jest.fn(() => {
    return {
        search: "test"
    }
})

jest.mock("react-router-dom", () => {
    return {
        useLocation: () => mockLocation()
    }
})

// 使用

test("useTabChange", () => {
    const { result, rerender } = renderHook(() => useTabChange());
    expect(mockLocation).toBeCalled()
})

We can even simulate the return of any function inside the method

Example 2: An'opa-fe-base' library is used in the business, we need to simulate the return

import { renderHook, act } from '@testing-library/react-hooks';
let mockFn = jest.fn()
jest.mock("opa-fe-base", () => ({ storeAPI: { getEntity: () => mockFn() } }))
import useCountry from '../useCountry'
​
describe("useCountry", () => {
    test("useCountry to return SG", () => {
        const { result, rerender } = renderHook(() => useCountry())
        expect(result.current).toBe('SG')
        mockFn = jest.fn(() => {
            return { country: "ID" }
        })
        rerender()
        act(() => {
            expect(result.current).toBe('ID')
        })
      
    })
})

As you can see in the above example, we have simulated the library'opa-fe-base' and returned a library containing

storeAPI object, there is a mock method inside the object. We can determine whether the return meets expectations by changing the return of the mock method.

Note: The function import that needs to be tested is written below our mock

When writing a single test recently, it was discovered that the window.URL.createObjectURL event can be directly simulated using jest.fn. However, the simulation of the document event is unsuccessful, and jest.spyOn can be used to query the document.

Scenario 3: Simulate document events through jest.spyOn

const spyFn = jest.spyOn(document, 'createElement')
const mockRevoke = jest.fn();
​
describe("downloadBlobFile", () => {
    beforeEach(() => {
        window.URL.createObjectURL = jest.fn((blob) => {
            return blob
        })
        window.URL.revokeObjectURL = mockRevoke;
    })
    test("downloadBlobFile must be called", () => {
        downloadBlobFile({});
        expect(spyFn).toBeCalled()
        expect(mockRevoke).toBeCalled();
    })
​
    test("download createObjectUrl use", () => {
        downloadBlobFile("test");
        expect(window.URL.createObjectURL.mock.calls[0][0]).toEqual('test')
    })
})

4. Snapshot

Help us ensure that the UI of the component will not be changed during the process of maintaining the code.

expect(组件实例).toMatchSnapshot()

A snapshot is generated during the first execution, and then it will be compared whether the snapshot is the same every time.

5. Recommend a vs code jest plug-in

Jest Runner

image.png

This plugin can render buttons in our test file to help us run or debug a test or describe separately. No need to run globally. Improve efficiency.

image.png

finally

The preparation of a single test is meaningful, but it is often confined to the business at work, there is no opportunity to write, and it is feasible and cherish~~


greet_eason
482 声望1.4k 粉丝

技术性问题欢迎加我一起探讨:zhi794855679