Jest單元測試配置和所遇問題解決辦法

原文地址: github.com/yinxin630/b…
技術交流: fiora.suisuijiang.com/html

Jest(jestjs.io/) 是由 Facebook 推出的一款優秀的測試框架, 它集成了斷言+測試的功能, 無須組合其餘工具便可實現單元測試node

上手體驗

首先須要安裝 Jest. npm i -D jestreact

建立源碼目錄 src, 編寫將要測試的方法git

// src/add.js
module.exports = function add(a, b) {
    return a + b;
}
複製代碼

建立測試目錄 __test__, 編寫第一個測試用例github

// __test__/add.test.js
const add = require('../src/add');
test('add()', () => {
    expect(add(1, 2)).toBe(3);
});
複製代碼

在 package.json 中, 添加 scripts 測試命令, "test": "jest"web

執行 npm test 運行單元測試, 結果以下 typescript

image

__test__ 是 Jest 默認測試目錄, 如需使用其餘目錄能夠在配置文件中修改npm

查看測試覆蓋率

能夠修改 Jest 配置啓用覆蓋率輸出, 在根目錄建立配置文件 jest.config.js, 添加以下內容json

module.exports = {
    collectCoverage: true,
}
複製代碼

從新執行單元測試, 結果以下 瀏覽器

image

同時在你的項目中會生成 coverage 目錄, 這裏面是 web 版的詳細覆蓋率報告

咱們先在 package.json 新增一個命令, 來快捷打開 web 版覆蓋率報告
添加 "coverage": "open ./coverage/lcov-report/index.html"
執行 npm run coverage 查看報告

image

添加 TypeScript 支持

首先, 將 add.js 修改成 add.ts

// src/add.ts
export default function add(a: number, b: number) {
    return a + b;
}
複製代碼

add.test.js 修改成 add.test.ts

// __test__/add.test.ts
import add from '../src/add';
test('add()', () => {
    expect(add(1, 2)).toBe(3);
});
複製代碼

新增 tsconfig.json 添加 TypeScript 配置

// tsconfig.json
{
    "compilerOptions": {
        "target": "es5",
        "strict": true,
    },
    "include": [
        "src/**/*",
        "__test__/**/*"
    ],
    "exclude": [
        "node_modules",
    ]
}
複製代碼

使用 Jest 測試 TypeScript 代碼須要藉助 ts-jest 解析器
安裝依賴 npm i -D ts-jest typescript @types/jest

修改 Jest 配置文件, 將 ts 文件解析器設置爲 ts-jest

// jest.config.js
module.exports = {
    collectCoverage: true,
    transform: {
        '^.+\\.tsx?$': 'ts-jest',
    },
}
複製代碼

從新執行 npm test 查看結果

添加 React 支持

首先安裝相關依賴
npm i --save react react-dom
npm i -D @types/react @types/react-dom

修改 tsconfig.json 添加 tsx 支持

// tsconfig.json
{
    "compilerOptions": {
        ...
        "jsx": "react",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true
    },
    ...
}
複製代碼

測試 React 代碼, 還須要藉助 enzyme(airbnb.io/enzyme/), 這是由 Airbnb 出的一個 React 測試工具

安裝依賴
npm i -D enzyme enzyme-adapter-react-16 @types/enzyme @types/enzyme-adapter-react-16

新增一個 React 組件 Example

// src/Example.tsx
import React from 'react';
export default function Example() {
    return (
        <div>Example</div>
    )
}
複製代碼

新增 Example 組件測試用例

// __test__/Example.test.tsx
import React from 'react';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

import Example from '../src/Example';

configure({ adapter: new Adapter() });

test('<Example>', () => {
    const example = mount(<Example/>);
    expect(example).toMatchSnapshot({});
    expect(example.html()).toBe('<div>Example</div>');
})
複製代碼

執行 npm test -- __test__/Example.test.tsx 單獨測試 Example 組件, 結果以下

image

React 經常使用測試場景

傳遞和更新 props

新增一個有 props 的組件 Message

// src/Message.tsx
import React from 'react';
interface MessageProps {
    msg: string;
}
export default function Message(props: MessageProps) {
    return (
        <div>{props.msg}</div>
    )
}
複製代碼

編寫 Message 組件的測試用例

// __test__/Message.test.tsx
...
test('<Message>', () => {
    const message = mount(<Message msg="初始消息" />);
    expect(message.html()).toBe('<div>初始消息</div>');
    // 更新 props
    message.setProps({ msg: '更新消息' });
    expect(message.html()).toBe('<div>更新消息</div>');
})
複製代碼

模擬觸發事件

新增一個監聽點擊事件的組件 Count

// src/Count.tsx
import React, { useState } from 'react';
export default function Count() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <span>{count}</span>
            <button onClick={() => setCount(count + 1)}>+1</button>
        </div>
    )
}
複製代碼

編寫 Count 組件的測試用例

// __test__/Count.test.tsx
...
test('<Count>', () => {
    const count = mount(<Count/>);
    expect(count.find('span').text()).toBe('0');
    // 模擬 click 事件
    count.find('button').simulate('click');
    expect(count.find('span').text()).toBe('1');
})
複製代碼

實踐中遇到的問題和解法

瀏覽器 API 不支持的狀況

Jest 默認下是用 jsdom(github.com/jsdom/jsdom) 這個虛擬環境來運行測試的, 它是一個仿瀏覽器環境, 可是並不支持全部的瀏覽器 API, 好比 URL.createObjectURL 就是不支持的

對於不支持的 API, 須要咱們對其添加 faker function

// @ts-ignore
global.URL.createObjectURL = jest.fn(() => 'faker createObjectURL');
複製代碼

注意, 必定要保證在調用 API 以前就已經注入了 polyfill, 好比某些模塊可能包含自執行代碼, 在 import 該模塊的時候, 就開始調用 API 了, 因此須要將 polyfill 放在 import 以前

調用 setTimeout 的代碼

仍是以 Count 組件爲例, 修改一下邏輯, 再也不點擊按鈕時自增, 修改成 mounted 1000ms 後自增一次

// src/Count.tsx
import React, { useState, useEffect } from 'react';
export default function Count() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        setTimeout(() => setCount(count + 1), 1000);
    }, []);
    return (
        <div>
            <span>{count}</span>
        </div>
    )
}
複製代碼

要測試 setTimeout, 須要使用 Jest 提供的 faker timer

同時還要注意, 使用 faker timer 後, react-dom 要求將更新 state 的邏輯包在 act 方法中, 不然就會出現以下的警告信息
Warning: An update to Count inside a test was not wrapped in act(...).

完整的測試用例以下所示

// __test__/Count.test.tsx
import React from 'react';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { act } from 'react-dom/test-utils';
import Count from '../src/Count';
configure({ adapter: new Adapter() });
// 注入 faker timers
jest.useFakeTimers();

test('<Count>', () => {
    const count = mount(<Count/>);
    expect(count.find('span').text()).toBe('0');
    act(() => {
        // 等待 1000ms
        jest.advanceTimersByTime(1000);
    })
    expect(count.find('span').text()).toBe('1');
})
複製代碼
相關文章
相關標籤/搜索