Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jest单元测试配置和所遇问题解决办法 #22

Open
yinxin630 opened this issue Aug 1, 2019 · 6 comments
Open

Jest单元测试配置和所遇问题解决办法 #22

yinxin630 opened this issue Aug 1, 2019 · 6 comments

Comments

@yinxin630
Copy link
Owner

yinxin630 commented Aug 1, 2019

Jest(https://jestjs.io/) 是由 Facebook 推出的一款优秀的测试框架, 它集成了断言+测试的功能, 无须组合其他工具即可实现单元测试

上手体验

首先需要安装 Jest. npm i -D jest

创建源码目录 src, 编写将要测试的方法

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

创建测试目录 __test__, 编写第一个测试用例

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

在 package.json 中, 添加 scripts 测试命令, "test": "jest"

执行 npm test 运行单元测试, 结果如下
image

__test__ 是 Jest 默认测试目录, 如需使用其他目录可以在配置文件中修改

查看测试覆盖率

可以修改 Jest 配置启用覆盖率输出, 在根目录创建配置文件 jest.config.js, 添加如下内容

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(https://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(https://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');
})
@zy-zero
Copy link

zy-zero commented Nov 19, 2019

你好,请问用过axios请求过
http://host:port/api
这样的API吗?
使用http协议得时候,jest会报
Timeout - Async callback was not invoked within the 5000ms timeout specified by jes timeout specified by jest.setTimeout.Error
使用https得时候,会正常通过,请问又配置可以解决这个问题吗?

@yinxin630
Copy link
Owner Author

你好,请问用过axios请求过
http://host:port/api
这样的API吗?
使用http协议得时候,jest会报
Timeout - Async callback was not invoked within the 5000ms timeout specified by jes timeout specified by jest.setTimeout.Error
使用https得时候,会正常通过,请问又配置可以解决这个问题吗?

是不是接口只能通过 HTTPS 请求呢, 有示例代码吗

@zy-zero
Copy link

zy-zero commented Nov 19, 2019

image
image
是这样的,我这里的request本地使用了http
然后线上测试使用了https
跑jest的时候, https可以通过,但是http就会报timeout
后面是我react-redux的代码和jest:
image

image

@liuzhongbao
Copy link

Uploading image.png…

@yinxin630
Copy link
Owner Author

@liuzhongbao 你的图没传上来...

@yinxin630
Copy link
Owner Author

@zy-zero 推荐 mock request 方法, 单测的时候不必真的发请求, 那样太慢了. 看下 jest.mock()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants