# 测试工具 (Test Utils)
包含了被 Koishi 使用的测试工具,它们由 koishi-test-utils
包提供。
注意
本页显示的版本号都表示对应的 koishi-test-utils 版本号(而不是对应的 koishi 版本号)。
# testDatabase(options, hooks)
测试全部内置数据库方法。
- options:
AppOptions
App 的构造函数的 database 字段,参见 使用数据库 - hooks: 测试中执行的钩子函数,每一个函数都传入一个 App 实例作为参数:
- hooks.beforeEachUser: 当每个用户测试执行前调用
- hooks.afterEachUser: 当每个用户测试执行后调用
- hooks.beforeEachGroup: 当每个群测试执行前调用
- hooks.afterEachGroup: 当每个群测试执行后调用
- 返回值:
App
用于测试的 App 实例
# createHttpServer(token?) 1.1.0+
模拟一个 CQHTTP HTTP 服务器。
- token:
string
验证字段 - 返回值:
Promise<HttpServer>
# createWsServer(token?) 1.1.0+
模拟一个 CQHTTP WebSocket 服务器。
- token:
string
验证字段 - 返回值:
Promise<WsServer>
# 类:MockedServer 2.0.0+
封装了一些用于测试的 API。上面所说的 HttpServer, WsServer 都是它的子类,而下面要介绍的 MockedApp 也实现了它的所有方法。
# mocked.shouldHaveLastRequest(method, params?)
断言最后发送的请求,并清空请求列表。
- method:
string
请求名称,不用写 async - params:
Record<string, any>
请求参数,可以略去部分 - 返回值:
void
# mocked.shouldHaveLastRequests(requests)
按时间顺序断言最后发送的若干个请求,并清空请求列表。
- requests:
[string, Record<string, any>?][]
请求的名称和参数 - 返回值:
void
# mocked.shouldHaveNoRequests()
断言没有发送任何请求。
- 返回值:
void
# mocked.shouldMatchSnapshot(name?) 3.0.0+
断言发送的请求与快照相符。
- name:
string
快照名 - 返回值:
void
# mocked.clearRequests()
清空请求列表。
- 返回值:
void
# mocked.setResponse(method, data, retcode?)
预先设置客户端请求的结果。
- method:
string
请求名称 - data: 响应数据,支持以下两种格式:
Record<string, any>
响应的 data 字段(params: Record<string, any>) => Partial<CQResponse>
传入请求参数,返回的对象将作为响应本身 2.0.0+
- retcode:
number
返回的错误码,默认为0
(仅对 data 不是函数的时候有效) - 返回值:
void
# 类:MockedApp 1.1.0+
MockedApp 是最常用的测试工具类。它是一个无需网络的 App 实例。借助它你可以方便地创建到 Koishi 的上报数据和处理 Koishi 发出的请求,从而完成对业务代码的测试工作。
MockedApp 会截获从 Sender API 发出的所有请求,因而实现了 MockedServer 的方法。
# new MockedApp(options?)
创建一个无网络 App 实例。参见 模拟事件上报。
# app.receive(meta) 2.0.0+
模拟一次事件上报。
- meta:
Meta
事件元信息对象 - 返回值:
void
# app.receiveMessage(type, message, userId, ctxId?) 2.0.0+
模拟一次 message 事件上报。当这个消息对应的 after-middleware 事件触发时返回。
- type:
'private' | 'group' | 'discuss'
事件的类型 - message:
string
消息文本 - userId:
number
发消息者 QQ 号 - ctxId:
number
上下文 ID(如果是群消息这里就是群号;如果是讨论组消息这里就是讨论组号;如果是私聊则这个参数不用写) - 返回值:
Promise<void>
此外,你也可以直接向这个方法传入一个等价的 Meta 对象,效果相同。
# app.receiveFriendRequest(userId, flag?) 2.0.0+
模拟一次 request/friend 事件上报。
- userId:
number
用户 QQ 号 - flag:
string
请求 flag,默认为flag
- 返回值:
void
# app.receiveGroupRequest(type, userId, groupId?, flag?) 3.0.0+
模拟一次 request/group 事件上报。
- userId:
number
用户 QQ 号 - type:
'add' | 'invite'
事件的子类型 - groupId:
number
群号 - flag:
string
请求 flag,默认为flag
- 返回值:
void
# app.receiveGroupUpload(file, userId, groupId?) 3.0.0+
模拟一次 group-upload 事件上报。
- groupId:
number
群号 - userId:
number
上传者 QQ 号 - groupId:
number
群号 - 返回值:
void
# app.receiveGroupAdmin(subType, userId, groupId?) 3.0.0+
模拟一次 group-admin 事件上报。
- subType:
'set' | 'unset'
事件的子类型 - userId:
number
目标 QQ 号 - groupId:
number
群号 - 返回值:
void
# app.receiveGroupIncrease(subType, userId, groupId?, operatorId?) 3.0.0+
模拟一次 group-increase 事件上报。
- subType:
'approve' | 'invite'
事件的子类型 - userId:
number
目标 QQ 号 - groupId:
number
群号 - operatorId:
number
操作者 QQ 号 - 返回值:
void
# app.receiveGroupDecrease(subType, userId, groupId?, operatorId?) 3.0.0+
模拟一次 group-decrease 事件上报。
- subType:
'leave' | 'kick' | 'kick_me'
事件的子类型 - userId:
number
目标 QQ 号 - groupId:
number
群号 - operatorId:
number
操作者 QQ 号 - 返回值:
void
# app.receiveGroupBan(subType, duration, userId, groupId?, operatorId?) 3.0.0+
模拟一次 group-ban 事件上报。
- subType:
'ban' | 'lift_ban'
事件的子类型 - duration:
number
禁言时长 - userId:
number
目标 QQ 号 - groupId:
number
群号 - operatorId:
number
操作者 QQ 号 - 返回值:
void
# app.receiveFriendAdd(userId) 3.0.0+
模拟一次 friend-add 事件上报。
- userId:
number
目标 QQ 号 - 返回值:
void
# app.createSession(ctxType, userId, ctxId?)
创建一个会话。
- ctxType:
'user' | 'group' | 'discuss'
上下文类型 - userId:
number
发言用户 ID - ctxId:
number
群号或讨论组号(如果是私聊则不需要这个参数) - 返回值:
Session
会话对象
# 类:Session 1.1.0+
会话是对同一上下文的多次消息的一个抽象。它使用 app.createSession()
方法创建,并借助 app.receiveMessage()
实现其功能。因此,这个类下的大部分方法的返回都基于 after-middleware 事件。在提供了极大方便的同时,会话也存在一些限制。
注意
严格上说 after-middleware 事件不能够代表一条信息被处理完成,因为存在以下几种特殊情况:
- 异步的 message 事件监听器可能仍未处理完成
- 中间件和指令中可能存在未阻塞的异步操作
因此,如果你的插件存在上面的某种情况,这个类的方法可能会返回预料之外的结果。你可以通过手动调用 sleep()
函数,让测试等待一段时间,来完成剩下的操作。
注意
会话的另一个限制是它的实现基于 Meta 方法和 快捷操作 技术。因此它无法用于测试下列两种行为:
- 通过直接调用 Sender API 而非 Meta 方法的行为
- 对单一信息可能存在多条回复的行为
在这两种情况下,你仍然可以使用上面所述的 MockedServer API 来解决你的问题。
# session.send(message) 3.0.0+
模拟发送一条消息。
- message:
string
要发送的信息 - 返回值:
Promise<string[]>
收到的回复列表
# session.shouldHaveReply(message, reply?)
断言某条信息应存在某些回复。
- message:
string
要发送给机器人的信息 - reply:
string
应有的回复,如果略去则不会进行比较 - 返回值:
Promise<void>
# session.shouldMatchSnapshot(message)
断言某条信息应存在与快照一致的回复。
- message:
string
要发送给机器人的信息 - 返回值:
Promise<void>
# session.shouldHaveNoReply(message) 3.0.0+
断言某条信息不应存在任何回复。
- message:
string
要发送给机器人的信息 - 返回值:
Promise<void>
# 类:HttpServer 1.1.0+
用于测试的 CQHTTP HTTP 服务器。
# server.post(meta?)
向 Koishi 上报事件。
- meta:
Meta
事件元信息对象 - 返回值:
Promise<AxiosResponse<any>>
# server.createBoundApp(options?)
创建一个与当前服务器相关联的 App 实例。
# server.close()
关闭服务端和所有关联的 App 实例。
- 返回值:
Promise<void>
# 类:WsServer 1.1.0+
用于测试的 CQHTTP WebSocket 服务器。
# server.post(meta?)
向 Koishi 上报事件。
- meta:
Meta
事件元信息对象 - 返回值:
Promise<void>
# server.nextTick()
等待任意一条请求。由于 WsServer 的 post
方法无法得知访问结果,因此最好与之结合使用。
- 返回值:
Promise<void>
# server.createBoundApp(options?)
创建一个与当前服务器相关联的 App 实例。
# server.close()
关闭服务端和所有关联的 App 实例。
- 返回值:
Promise<void>
# koishi-utils 3.0.0+
koishi-test-utils 导出了一个 utils
对象作为 koishi-utils 的副本,同时增加了手动控制其中部分函数返回值的机制。所有的 random 函数和 sleep 函数都将是 mockFn 实例,你可以在 这里 看到它们的详细文档。
除此以外,koishi-test-utils 还扩展了以下几个方法:
# utils.randomPick.mockIndex(index)
控制此后 randomPick 函数返回的数组元素。
- index:
number
元素下标 - 返回值:
this
randomPick 函数本身
# utils.randomPick.mockIndexOnce(index)
控制下一次 randomPick 函数返回的数组元素。
- index:
number
元素下标 - 返回值:
this
randomPick 函数本身
# utils.randomSplice.mockIndex(index)
控制此后 randomSplice 函数返回的数组元素。
- index:
number
元素下标 - 返回值:
this
randomSplice 函数本身
# utils.randomSplice.mockIndexOnce(index)
控制下一次 randomSplice 函数返回的数组元素。
- index:
number
元素下标 - 返回值:
this
randomSplice 函数本身
# utils.randomMultiPick.mockIndices(...indices)
控制此后 randomMultiPick 函数返回的数组元素。
- indices:
number
元素下标列表 - 返回值:
this
randomMultiPick 函数本身
# utils.randomMultiPick.mockIndicesOnce(...indices)
控制下一次 randomMultiPick 函数返回的数组元素。
- indices:
number
元素下标列表 - 返回值:
this
randomMultiPick 函数本身