Jest spyon踩坑

1,212 阅读1分钟

问题描述

jest.spyOn(obj, 'methodName') 会返回一个 mock.fn 对象,多次 spyOn 并不会更新这个对象,而是返回同一个对象,所以,会影响方法被调用的断言。

expect(obj.methodName).toBeCalled();

最小复原代码

  • npm install --save-dev jest
  • 添加下面的测试代码 xxx.test.js
  • jest

可以看到 mock 打印出来的对象是同一个对象。有两次调用记录。

    console.log
      finally first = second: true

      at Suite.<anonymous> (jjjest/spy.test.js:32:11)

    console.log
      {
        calls: [ [ 'first' ] ],
        instances: [ APIService {} ],
        invocationCallOrder: [ 1 ],
        results: [ { type: 'return', value: [Promise] } ]
      }

      at Object.<anonymous> (jjjest/spy.test.js:21:13)

    console.log
      {
        calls: [ [ 'first' ], [ 'second' ] ],
        instances: [ APIService {}, APIService {} ],
        invocationCallOrder: [ 1, 2 ],
        results: [
          { type: 'return', value: [Promise] },
          { type: 'return', value: [Promise] }
        ]
      }

      at Object.<anonymous> (jjjest/spy.test.js:30:13)
// https://jestjs.io/zh-Hans/docs/mock-functions

class APIService {
  async getProduct(order) {
    return Promise.resolve("hello");
  }
}
async function getProduct(order) {
  const api = new APIService();
  await api.getProduct(order);
}
let first;
let second;
describe("spyOn will return the same Obj", () => {
  it("first ", async () => {
    first = jest.spyOn(APIService.prototype, "getProduct").mockResolvedValue("world");

    await getProduct("first");

    expect(APIService.prototype.getProduct).toBeCalled();
    console.log(APIService.prototype.getProduct.mock);
  });

  it("second ", async () => {
    second = jest.spyOn(APIService.prototype, "getProduct").mockResolvedValue("world");

    await getProduct("second");

    expect(APIService.prototype.getProduct).toBeCalled();
    console.log(APIService.prototype.getProduct.mock);
  });
  console.log("finally first = second:", first === second);
});

解决方法

  • 给 spyOn 的对象命名,在 beforeEach 进行 mockFn.mockClear
  • 不要 mock prototype,针对每一个测试用例,生成单独的测试对象,这个比较冗余,可能不适合。

\

// https://jestjs.io/zh-Hans/docs/mock-functions

class APIService {
  async getProduct(order) {
    return Promise.resolve("hello");
  }
}
async function getProduct(order) {
  const api = new APIService();
  await api.getProduct(order);
}
let mockApiGetProduct = jest.spyOn(APIService.prototype, "getProduct");

describe("spyOn will return the same Obj", () => {
  beforeEach(() => {
    mockApiGetProduct.mockClear();
  });
  it("first ", async () => {
    mockApiGetProduct.mockResolvedValue("world");

    await getProduct("first");

    expect(mockApiGetProduct).toBeCalled();
    console.log(mockApiGetProduct.mock);
  });

  it("second ", async () => {
    mockApiGetProduct.mockResolvedValue("world-again");

    await getProduct("second");

    expect(mockApiGetProduct).toBeCalled();
    console.log(mockApiGetProduct.mock);
  });
});
    console.log
      {
        calls: [ [ 'first' ] ],
        instances: [ APIService {} ],
        invocationCallOrder: [ 1 ],
        results: [ { type: 'return', value: [Promise] } ]
      }

      at Object.<anonymous> (jjjest/restore.test.js:24:13)

    console.log
      {
        calls: [ [ 'second' ] ],
        instances: [ APIService {} ],
        invocationCallOrder: [ 2 ],
        results: [ { type: 'return', value: [Promise] } ]
      }

      at Object.<anonymous> (jjjest/restore.test.js:32:13)