0%

umijs-unit-test

最近的项目使用阿里umijs框架,在做Unit Test时遇到了一些问题,在此记录一下,以供日后查阅。

创建Umi项目

使用以下命令创建一个新的Umi项目,项目模版选择ant-design-pro,npm client选择npm,npm registry选择npm

1
npx create-umi@latest

生成jest配置

Umi的方便之处是可以一键生成jest配置,只需运行如下命令即可:

1
npx umi g jest

运行测试

使用以下命令运行测试

1
npm test

这时如果不出意外,你会遇到第一个错误:

第一个错误

1
2
3
Error: Jest: Failed to parse the TypeScript config file D:\personal\codes\react\umi-app-umi-jest\jest.config.ts
TSError: ⨯ Unable to compile TypeScript:
error TS5095: Option 'bundler' can only be used when 'module' is set to 'preserve' or to 'es2015' or later.

看提示信息大概能猜到问题的原因,就是tsconfig.json中的moduleResolution配置与module配置之间有冲突。解决办法是修改moduleResolution的配置。打开项目根目录下的tsconfig.json文件,并设置moduleResolutionnode,如下所示:

1
2
3
4
5
6
{
"extends": "./src/.umi/tsconfig.json",
"compilerOptions": {
"moduleResolution": "node" //这里设置为"node"
}
}

再次运行测试npm test,这时会遇到第二个错误:

第二个错误

1
AssertionError [ERR_ASSERTION]: Invalid config keys: antd, access, model, initialState, request, layout

这是因为我们的项目模板是ant-design-pro,自动集成了max,所以应该使用max来生成jest配置。
首先删除jest.config.ts文件,然后使用以下命令重新生成jest配置:

1
npx max g jest

再次运行测试,已经没有错误了,但是会提示没有测试用例,所以我们添加一个测试用例。

我们在src/pages下添加一个login目录,并在该目录下添加一个index.tsx文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { history } from '@umijs/max';
import { Button, Card, Form, Input, message } from 'antd';
import React from 'react';
import styles from './index.less';

interface LoginParams {
username: string;
password: string;
}

const LoginPage: React.FC = () => {
const [form] = Form.useForm();
const [loading, setLoading] = React.useState(false);

const handleSubmit = async (values: LoginParams) => {
try {
setLoading(true);
// Replace this with your actual login API call
console.log('Login attempt with:', values);

// Simulating API delay
await new Promise(resolve => setTimeout(resolve, 1000));

// On successful login
message.success('Login successful!');
history.push('/');
} catch (error) {
message.error('Login failed. Please check your credentials.');
console.error('Login error:', error);
} finally {
setLoading(false);
}
};

return (
<div className={styles.container}>
<Card className={styles.loginCard} title="Login" bordered={false}>
<Form
form={form}
name="login"
layout="vertical"
requiredMark={false}
onFinish={handleSubmit}
>
<Form.Item
name="username"
rules={[{ required: true, message: 'Please enter your username!' }]}
>
<Input
prefix={<UserOutlined />}
placeholder="Username"
size="large"
/>
</Form.Item>

<Form.Item
name="password"
rules={[{ required: true, message: 'Please enter your password!' }]}
>
<Input.Password
prefix={<LockOutlined />}
placeholder="Password"
size="large"
/>
</Form.Item>

<Form.Item>
<Button
type="primary"
htmlType="submit"
size="large"
block
loading={loading}
>
Log in
</Button>
</Form.Item>
</Form>
</Card>
</div>
);
};

export default LoginPage;

然后添加一个样式文件index.less,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f2f5;
}

.loginCard {
width: 100%;
max-width: 400px;

:global {
.ant-card-head-title {
text-align: center;
font-size: 24px;
font-weight: 500;
}
}
}

最后再添加一个测试用例index.test.tsx,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { render, screen } from '@testing-library/react';
import LoginPage from './index';

describe('LoginPage', () => {
it('renders the login form correctly', () => {
render(<LoginPage />);

// Check that form elements are rendered
expect(screen.getByPlaceholderText('Username')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /log in/i })).toBeInTheDocument();
});
});

再次运行测试,这时候你会遇到第三个错误:

第三个错误

1
TypeError: window.matchMedia is not a function

解决办法:在项目根目录下的jest.setup.ts文件中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query: unknown) => ({
matches: false,
media: query,
onchange: null,
addListener: () => { },
removeListener: () => { },
addEventListener: () => { },
removeEventListener: () => { },
dispatchEvent: () => { },
})
});

再次运行测试,这时候已经没有错误了,感谢您的观看,祝您编程愉快!