mock vs stub vs spy

Sinon 是用于 JavaScript 的测试框架,适用于任何单元测试框架。
Sinon 将测试为三种类型:
Spies:提供有关函数调用的信息,而不会影响其行为
Stubs:类似于 Spies,但完全取代了功能。这样就可以使存根函数做任何你喜欢的事情 - 抛出异常,返回一个特定的值等等
Mocks:通过组合 Spies 和 Stubs,可以更轻松地替换整个对象

spy、stub、mock 用法

假设我们有 Todo.ts 代码如下。

  1. // Todo.ts
  2. export default class Todo {
  3. private todos: string[] = [];
  4. private ajax: any;
  5. constructor(ajax?) {
  6. ajax && this.ajax = ajax;
  7. }
  8. add(content) {
  9. this.todos.push(content);
  10. }
  11. printFirstTodo() {
  12. console.log(this.todos[0]);
  13. }
  14. syncClound() {
  15. this.ajax && this.ajax.get("/todo.json", data => {
  16. this.todos = data;
  17. });
  18. }
  19. }

然后我们使用 Sinon 来为它做单元测试。分别使用 spy、stub、mock 来做单元测试。

spy

// Todo.test.ts
import * as sinon from "sinon";
// 引入 chai 作为断言库
import { expect } from "chai";
import Todo from "./Todo";

describe("测试 Todo", () => {
  it("spy print", () => {
    const t = new Todo();
    // 用 sinon.spy 监控 console.log 函数的执行,并不替换其原有的实现
    sinon.spy(console, "log");
    t.add("Go to school");
    t.add("Sleep");
    t.printFirstTodo();
    // @ts-ignore console.log.calledOnce 无法识别
    // 如果 spy 监控的 console.log 函数刚好被调用一次,则返回 true
    expect(console.log.calledOnce).to.be.true;
  });
});

测试结果
[资料] mock vs stub vs spy - 图1

stub

// Todo.test.ts
import * as sinon from "sinon";
// 引入 chai 作为断言库
import { expect } from "chai";
import Todo from "./Todo";

describe("测试 Todo", () => {
  it("stub syncTodoFromCloud", () => {
    const t = new Todo();
    // 用 stub 模拟 t.add 函数,stubAdd 函数被模拟为一个空函数
    const stubAdd = sinon.stub(t, "add").callsFake(() => {});
    // 执行被模拟的 stubAdd 函数,这时候 'Go to school' 并没有被真正地 push
    stubAdd("Go to school");
    // 尝试打印,会打印出 undefined
    t.printFirstTodo();
    // 我们期望 stubAdd 被执行了一次
    expect(stubAdd.calledOnce).to.be.true;
  });
});

测试结果
[资料] mock vs stub vs spy - 图2

mock

// Todo.test.ts
import * as sinon from "sinon";
// 引入 chai 作为断言库
import { expect } from "chai";
import Todo from "./Todo";

describe("测试 Todo", () => {
  it("mock syncTodoFromCloud", () => {
    const t = new Todo();
    // 这时候 console 已经被 mock 完全 mock 了
    // 这里可以调用console下的任何方法,但并不会执行
    const mock = sinon.mock(console);
    // 由于 console 已经完全被 mock了,所以我们这里可以提前描述我们预期的行为
    mock.expects("log").calledOnce;
    t.add("Go to school");
    t.printFirstTodo();
    // 校验
    mock.verify();
  });
});

测试结果
[资料] mock vs stub vs spy - 图3
最后我们用一张图来总结一下三者的区别
[资料] mock vs stub vs spy - 图4