- 请注意,如果您在 Windows 上使用 SQLite 数据库,则本指南将不起作用,因为 Windows 锁定 SQLite 文件的方式。
- 此页面正在进行中,可能已过时。一旦 Strapi v5 推出,它将被大量重新制作。
安装测试工具
Jest
包含一组用于创建和设计测试用例的指南或规则 - 旨在帮助测试人员更有效地进行测试的实践和工具的组合。
Supertest
允许您测试所有 api
路由,因为它们是 http.Server 的实例。
sqlite3
用于创建在测试之间创建和删除的磁盘数据库。
yarn add --dev jest supertest sqlite3
完成后,将其添加到 package.json
文件中
将 test
命令添加到 scripts
部分
{
"scripts": {
"develop": "strapi evolve",
"start": "strapi start",
"build": "strapi build",
"strapi": "strapi",
"test": "jest --forceExit --detectOpenHandles"
}
}
并在文件底部添加这些行
{
"jest": {
"testPathIgnorePatterns": [
"/node_modules/",
".tmp",
".cache"
],
"testEnvironment": "node"
}
}
这些将通知Jest
不要在它不应该的文件夹中寻找测试。
设置测试环境
测试框架必须有一个干净的空环境才能执行有效测试,并且不会干扰当前数据库。
一旦 jest
运行,它就会使用 test
环境(将 NODE_ENV
切换为 test
)
因此我们需要为此创建一个特殊的环境设置。
为测试环境 ./config/env/test/database.js
创建一个新配置,并添加以下值 "filename": ".tmp/test.db"
,这样做的原因是我们希望有一个单独的 sqlite 数据库用于测试,这样我们的测试就不会触及真实数据。
此文件将是临时的,每次测试完成后,我们都会删除每次在干净的数据库上运行测试时的文件。
整个文件将如下所示:
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: env('DATABASE_FILENAME', '.tmp/test.db'),
},
useNullAsDefault: true,
debug: false
},
})
创建 Strapi 实例
为了测试任何东西,我们需要有一个在测试环境中运行的 strapi 实例, 基本上我们希望将 strapi 应用程序的实例作为对象,类似于为 进程管理器 创建实例。
这些任务需要添加一些文件 - 让我们创建一个文件夹“tests”,所有测试都将放在里面,旁边是文件夹“helpers”,主要 Strapi 助手将位于文件 strapi.js 中。
const fs = require('node:fs')
const Strapi = require('@strapi/strapi')
let instance
async function setupStrapi() {
if (!instance) {
await Strapi().load()
instance = strapi
await instance.server.mount()
}
return instance
}
async function cleanupStrapi() {
const dbSettings = strapi.config.get('database.connection')
// 关闭服务器以释放数据库文件
await strapi.server.httpServer.close()
// 删除前关闭与数据库的连接
await strapi.db.connection.destroy()
// 所有测试完成后删除测试数据库
if (dbSettings && dbSettings.connection && dbSettings.connection.filename) {
const tmpDbFile = dbSettings.connection.filename
if (fs.existsSync(tmpDbFile)) {
fs.unlinkSync(tmpDbFile)
}
}
}
module.exports = { setupStrapi, cleanupStrapi }
测试 Strapi 实例
我们需要一个用于测试的主入口文件,该文件还将测试我们的辅助文件。
const fs = require('node:fs')
const { setupStrapi, cleanupStrapi } = require('./helpers/strapi')
beforeAll(async () => {
await setupStrapi()
})
afterAll(async () => {
await cleanupStrapi()
})
it('strapi is defined', () => {
expect(strapi).toBeDefined()
})
实际上这就是我们编写单元测试所需要的全部内容。只需运行“yarn test”并查看第一个测试的结果
yarn run v1.13.0
$ jest
PASS tests/app.test.js
✓ strapi is defined (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.187 s
Ran all test suites.
✨ Done in 5.73s.
如果您收到 Jest 的超时错误,请在 app.test.js
文件中的 beforeAll
方法之前添加以下行:jest.setTimeout(15000)
,并根据需要调整毫秒值。
测试基本端点控制器
在示例中,我们将使用来自 controllers 部分的示例 Hello world
/hello
端点。
有人可能会说 API 测试不是单元测试,而是有限的集成测试,无论命名方式如何,让我们继续测试第一个端点。
我们将测试我们的端点是否正常工作,并且路由/hello
是否返回Hello World
让我们创建一个单独的测试文件,其中将使用supertest
来检查端点是否按预期工作。
const request = require('supertest')
it('should return hello world', async () => {
await request(strapi.server.httpServer)
.get('/api/hello')
.expect(200) // 期望响应 http 代码 200
.then((data) => {
expect(data.text).toBe('Hello World!') // 期望响应文本
})
})
然后将此代码包含到该文件底部的 ./tests/app.test.js
中
require('./hello')
然后运行yarn test
,它应该返回
➜ my-project yarn test
yarn run v1.13.0
$ jest --detectOpenHandles
PASS tests/app.test.js (5.742 s)
✓ strapi is defined (4 ms)
✓ should return hello world (208 ms)
[2020-05-22T14:37:38.018Z] debug GET /hello (58 ms) 200
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.635 s, estimated 7 s
Ran all test suites.
✨ Done in 9.09s.
如果您收到错误“Jest 检测到以下 1 个打开的句柄可能阻止 Jest 退出”,请检查“jest”版本,因为“26.6.3”可以正常工作。
测试 auth
端点控制器
在此场景中,我们将使用两个测试来测试身份验证登录端点
- 测试
/auth/local
,它应登录用户并返回jwt
令牌 - 测试
/users/me
,它应根据Authorization
标头返回用户数据
const request = require('supertest')
// user mock data
const mockUserData = {
username: 'tester',
email: 'tester@strapi.com',
provider: 'local',
password: '1234abc',
confirmed: true,
blocked: null,
}
it('should login user and return jwt token', async () => {
/** 创建新用户并将其保存到数据库 */
await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
})
await request(strapi.server.httpServer) // 应用服务器是类的一个实例:http.Server
.post('/api/auth/local')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.send({
identifier: mockUserData.email,
password: mockUserData.password,
})
.expect('Content-Type', /json/)
.expect(200)
.then((data) => {
expect(data.body.jwt).toBeDefined()
})
})
it('should return users data for authenticated user', async () => {
/** 获取默认用户角色 */
const defaultRole = await strapi.query('plugin::users-permissions.role').findOne({}, [])
const role = defaultRole ? defaultRole.id : null
/** Creates a new user an push to database */
const user = await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
username: 'tester2',
email: 'tester2@strapi.com',
role,
})
const jwt = strapi.plugins['users-permissions'].services.jwt.issue({
id: user.id,
})
await request(strapi.server.httpServer) // 应用服务器是类的一个实例:http.Server
.get('/api/users/me')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${jwt}`)
.expect('Content-Type', /json/)
.expect(200)
.then((data) => {
expect(data.body).toBeDefined()
expect(data.body.id).toBe(user.id)
expect(data.body.username).toBe(user.username)
expect(data.body.email).toBe(user.email)
})
})
然后将此代码添加到该文件底部的 ./tests/app.test.js
require('./user')
上述所有测试都应返回类似这样的控制台输出
➜ my-project git:(master) yarn test
yarn run v1.13.0
$ jest --forceExit --detectOpenHandles
[2020-05-27T08:30:30.811Z] debug GET /hello (10 ms) 200
[2020-05-27T08:30:31.864Z] debug POST /auth/local (891 ms) 200
PASS tests/app.test.js (6.811 s)
✓ strapi is defined (3 ms)
✓ should return hello world (54 ms)
✓ should login user and return jwt token (1049 ms)
✓ should return users data for authenticated user (163 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 6.874 s, estimated 9 s
Ran all test suites.
✨ Done in 8.40s.