Skip to main content

测试替身类型

测试替身一共有五种类型:

  • 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");
});