介绍
在本文中,我将带您完成设置快速 +打字条项目的过程,并演示如何有效模拟外部依赖关系。我创建了一个演示应用程序来展示此概念。
项目设置
创建新的快速项目
mkdir fastify-ts-mock
cd fastify-ts-mock
npm init -y
安装打字稿
npm i -D typescript @types/node ts-node
tsc --init
安装快速
npm i fastify fastify-plugin @fastify/autoload
文件夹结构
(root) fastify-ts-mock
|-- src
|-- lib
|-- plugins
|-- routes
|-- type
|-- test
路径别名
npm i module-alias
npm i -D @types/module-alias tsconfig-paths
将以下行添加到tsconfig.json
{
"ts-node": {
"require": ["tsconfig-paths/register"]
},
...
"baseUrl": "./",
"paths": {
"@src/*": ["src/*"],
"@type/*": ["src/type/*"],
"@lib/*": ["src/lib/*"],
"@test/*": ["test/*"],
},
...
}
快速应用程序
文件:src/app.ts
import 'module-alias/register';
import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify'
import autoload from '@fastify/autoload'
import { join } from 'path'
export default function createApp(
opts?: FastifyServerOptions,
): FastifyInstance {
const defaultOptions = {
logger: true,
}
const app = fastify({ ...defaultOptions, ...opts })
app.register(autoload, {
dir: join(__dirname, 'plugins'),
})
app.register(autoload, {
dir: join(__dirname, 'routes'),
options: { prefix: '/api' },
})
return app
}
列出所有设备路线(/api/devices
)
文件:src/routes/devices/list.ts
import { FastifyInstance, FastifyPluginOptions } from 'fastify'
import {DeviceDtoCollectionType} from "@type/devices.type";
export default async function (
fastify: FastifyInstance,
_opts: FastifyPluginOptions,
): Promise<void> {
fastify.get<{ Reply: DeviceDtoCollectionType }>(
'/',
async (request, reply) => {
try {
const devices = await fastify.listDevices()
return reply.send(devices)
} catch (error) {
request.log.error(error)
return reply.code(500).send()
}
},
)
}
列出所有设备功能
文件:src/plugins/features/devices.list.feature.ts
import fp from 'fastify-plugin'
import { FastifyInstance, FastifyPluginOptions } from 'fastify'
import * as DeviceLib from '@lib/devices.lib'
import {DeviceDtoCollectionType} from "@type/devices.type";
declare module 'fastify' {
interface FastifyInstance {
listDevices: () => Promise<DeviceDtoCollectionType>
}
}
async function listDevicesPlugin(
fastify: FastifyInstance,
_opts: FastifyPluginOptions,
): Promise<void> {
const listDevices = async (): Promise<DeviceDtoCollectionType> => DeviceLib.listDevices()
fastify.decorate('listDevices', listDevices)
}
export default fp(listDevicesPlugin)
此插件使用DeviceLib.listDevices
,这是一个外部依赖。
为了正确测试路线,我需要对其进行模拟。
安装测试库
npm i tap
npm i -D @types/tap
测试代码(test/routes/devices/list.test.ts
)
import {afterEach, test} from "tap"
import createApp from "@src/app";
import * as DeviceLib from '@lib/devices.lib'
import * as Fixtures from '@test/fixtures'
import {DeviceDtoCollectionType} from "@type/devices.type";
test('get all devices', async t => {
const app = createApp({
logger: false,
})
t.teardown(() => {
app.close();
})
const response = await app.inject({
method: 'GET',
url: '/api/devices',
})
const deviceCollection = response.json<DeviceDtoCollectionType>()
t.equal(response.statusCode, 200)
t.equal(deviceCollection.length, 2)
... other test assertion ...
})
第一种方法:用工厂装饰插件
async function listDevicesPlugin(
fastify: FastifyInstance,
_opts: FastifyPluginOptions,
): Promise<void> {
const listDevices = async (): Promise<DeviceDtoCollectionType> => {
return await DeviceLib.listDevices()
}
const listDevicesTest = async (): Promise<DeviceDtoCollectionType> => {
return [
... some fake data ...
]
}
function listDevicesFactory() {
if (fastify.config.ENV === 'test') {
return listDevicesTest()
}
return listDevices()
}
fastify.decorate('listDevices', listDevices)
}
专利:
- 简单的方法
- 不需要第三方图书馆
cons:
- 更多插件复杂性=更多模拟功能复杂性
- 维护的代码
- 不是错误的代码
更好的方法:使用ImportMock
安装依赖项:
npm i -D sinon ts-mock-imports
更新测试代码(test/routes/devices/list.test.ts
):
import {afterEach, test} from "tap"
import createApp from "@src/app";
import {ImportMock} from "ts-mock-imports";
import * as DeviceLib from '@lib/devices.lib'
import * as Fixtures from '@test/fixtures'
import {DeviceDtoCollectionType} from "@type/devices.type";
afterEach(() => {
ImportMock.restore();
})
test('get all devices', async t => {
const app = createApp({
logger: false,
})
t.teardown(() => {
app.close();
})
const listDevicesMock = ImportMock.mockFunction(
DeviceLib,
'listDevices',
Fixtures.devices
)
const response = await app.inject({
method: 'GET',
url: '/api/devices',
})
const deviceCollection = response.json<DeviceDtoCollectionType>()
t.equal(response.statusCode, 200)
t.equal(deviceCollection.length, 2)
... some data assertions ...
t.ok(listDevicesMock.calledOnce)
})
专利:
- 非常简单
- 易于维护
cons:
- 需要第三方库
我们不是在模拟快速插件,而是在插件中使用的库。
如果我们尝试模拟插件怎么办?
const listDevicesMock = ImportMock.mockFunction(
app,
'listDevices',
Fixtures.devices
)
它不起作用:我们得到此错误:
Cannot stub non-existent property listDevices
结论
简而言之,以正确的方式写作测试不仅重要 - 至关重要。适当的测试确保我们的代码按预期工作并保持可靠。因此,请记住,好的测试意味着很棒的软件!
随时查看repository的动手体验,并更深入地了解该过程。愉快的编码!