测试替身类型
测试替身一共有五种类型:
-
Dummy Object 哑元对象
-
Stub 存根/测试桩
-
Spy 间谍
-
Mock 模拟对象
-
Fake 伪造
Dummy 哑元对象
dummy 哑元对象其实就是占位符。
总结来说,哑元对象在单元测试中主要用于以下目的:
-
满足参数列表要求:允许测试开发者调用那些需要特定参数的方法,而不需要关心这些参数的实际行为或状态。
-
减少不必要的复杂性:通过避免创建复杂的对象或配置,简化测试代码的编写和理解。
-
区分测试关注点:明确指出测试用例中哪些参数是不重要的,哪些是需要被测试和验证的。
代码示例:
function sendEmail(message: Message, recipient: Recipient) {
console.log(message.subject);
console.log(message.body);
}
test("dummy", () => {
const message: Message = {
subject: "heihei",
body: "hahaha",
};
const dummyRecipient = {} as Recipient;
sendEmail(message, dummyRecipient);
});
注意
dummy object 的命名方式尽量采用 dummy 开头,这样代码可读性更高。
Stub 存根/测试桩
Stub 主要在间接输入的时候,被用于替代外部依赖,控制测试环境,控制被测内容等。
这个被测对象只有一部分是我们所关心的,不需要去测试整个对象。
Spy 间谍
Spy 主要是用于监控和记录对某些对象的调用情况。
它不会影响这些对象的行为。
Vitest 中提供的 Spy 实现 API 是 vi.spyOn(object, method, accessType)。
Mock 模拟对象
Mock 是 Stub 和 Spy 的结合体。
在测试框架的 API 里,其实 Mock, Stub 和 Spy 的边界是很模糊的。
但我们在使用的时候,心里要清楚是什么测试替身类型。
Fake 伪造
Fake 用于模拟复杂的真实对象行为,是测试对象的简化版的完整实现。
Stub & Mock 和 Fake 的区别:
- Stub 和 Mock 通常用于测试中的特定状态和行为验证,而不提供完整的实现。
- Fake 提供了一个可以实际工作的简化实现,但不关注特定交互的细节。
代码示例:
// 被测内容
// socket.ts
import io from "socket.io-client";
const listeners: Listen[] = [];
export let socket;
export function initSocket() {
socket = io("http://localhost:3000");
socket.on("message", (message) => {
listeners.forEach((listener) => {
listener(message);
});
});
return socket;
}
type Listen = (message: string) => void;
export function addListener(listen: Listen) {
listener.push(listen);
}
// Fake Object
class FakeSocket {
private listeners: { [key: string]: ((...args: any[]) => void)[] } = {};
on(event: string, listener: (...args: any[]) => void) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
trigger(event: string, ...args: any[]) {
const eventListeners = this.listeners[event];
if (eventListeners) {
eventListeners.forEach((listener) => listener(...args));
}
}
}
function io() {
return new FakeSocket();
}
vi.mock("socket.io-client", () => {
return {
default: io,
};
});
import { initSocket, addListener } from "./socket";
test("should handle message from the server", () => {
const listener = vi.fn();
const socket = initSocket();
addListener(listener);
socket.trigger("message", "hi");
expect(listener).toBeCalledWith("hi");
});