Jest:如何模拟类的一种特定方法

IT技术 javascript jestjs
2021-03-17 17:21:10

假设我有以下课程:

export default class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }
    sayMyName() {
        console.log(this.first + " " + this.last);
    }
    bla() {
        return "bla";
    }
}

假设我想创建一个模拟类,其中方法 'sayMyName' 将被模拟,而方法 'bla' 将保持原样。

我写的测试是:

const Person = require("../Person");

jest.mock('../Person', () => {
    return jest.fn().mockImplementation(() => {
        return {sayMyName: () => {
            return 'Hello'
        }};
    });
});


let person = new Person();
test('MyTest', () => {
    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");
})

第一个“expect”语句通过,这意味着“sayMyName”被成功模拟。但是,第二个“期望”失败并出现错误:

类型错误:person.bla 不是函数

我知道模拟类删除了所有方法。我想知道如何模拟一个类,以便只模拟特定的方法。

6个回答

使用jest.spyOn()模拟单个方法并保留其余方法的正确Jest方式。实际上,对此有两种略有不同的方法。

1.只在单个对象中修改方法

import Person from "./Person";

test('Modify only instance', () => {
    let person = new Person('Lorem', 'Ipsum');
    let spy = jest.spyOn(person, 'sayMyName').mockImplementation(() => 'Hello');

    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");

    // unnecessary in this case, putting it here just to illustrate how to "unmock" a method
    spy.mockRestore();
});

2.修改类本身,使所有实例都受到影响

import Person from "./Person";

beforeAll(() => {
    jest.spyOn(Person.prototype, 'sayMyName').mockImplementation(() => 'Hello');
});

afterAll(() => {
    jest.restoreAllMocks();
});

test('Modify class', () => {
    let person = new Person('Lorem', 'Ipsum');
    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");
});

为了完整起见,这就是您模拟静态方法的方式:

jest.spyOn(Person, 'myStaticMethod').mockImplementation(() => 'blah');
很遗憾官方文档没有说明这一点。官方文档是一堆令人费解的module工厂、使用对象字面量手动滚动的类模拟、存储在特殊目录中的内容以及基于属性名称的特殊情况处理。 jestjs.io/docs/en/...
2021-04-22 17:21:10
@sesamechicken 不确定我是否遵循始终通过的测试。您永远不应该直接测试模拟方法,这将是零意义的。通常的用例是模拟为您实际测试的不同方法提供数据的方法。
2021-04-26 17:21:10
>您永远不应该直接测试模拟方法这正是上面的示例使用mockImplementation.
2021-04-27 17:21:10
我已经在上面编辑了我的答案。@blade UsingmockImplementation将始终产生通过测试,如果类发生变化,则为误报。
2021-05-03 17:21:10
确实如此,但它回答了 OP 的问题。我认为在此答案中详细介绍正确测试是题外话,并且会偏离重点。
2021-05-11 17:21:10

编辑 05/03/2021

我看到很多人不同意下面的方法,这很酷。不过,我确实对@blade 的方法略有异议,因为它实际上并未测试该类,因为它使用mockImplementation. 如果类发生变化,测试仍将始终通过并给出误报。所以这里有一个例子spyOn

// person.js
export default class Person {
  constructor(first, last) {
      this.first = first;
      this.last = last;
  }
  sayMyName() {
      return this.first + " " + this.last; // Adjusted to return a value
  }
  bla() {
      return "bla";
  }
}

和测试:

import Person from './'

describe('Person class', () => {
  const person = new Person('Guy', 'Smiley')

  // Spying on the actual methods of the Person class
  jest.spyOn(person, 'sayMyName')
  jest.spyOn(person, 'bla')
  
  it('should return out the first and last name', () => {  
    expect(person.sayMyName()).toEqual('Guy Smiley') // deterministic 
    expect(person.sayMyName).toHaveBeenCalledTimes(1)
  });
  it('should return bla when blah is called', () => {
    expect(person.bla()).toEqual('bla')
    expect(person.bla).toHaveBeenCalledTimes(1)
  })
});

干杯! 🍻


我看不到模拟实现实际上如何为您解决任何问题。我觉得这更有意义

import Person from "./Person";

describe("Person", () => {
  it("should...", () => {
    const sayMyName = Person.prototype.sayMyName = jest.fn();
    const person = new Person('guy', 'smiley');
    const expected = {
      first: 'guy',
      last: 'smiley'
    }

    person.sayMyName();

    expect(sayMyName).toHaveBeenCalledTimes(1);
    expect(person).toEqual(expected);
  });
});
@Martin 是的,确实如此。
2021-04-22 17:21:10
我不知道这个问题的答案,所以真的很好奇:这会不会让 Person.prototype.sayMyName 为在此之后运行的任何其他测试而改变?
2021-05-04 17:21:10
有关如何使用 spyOn 在 Jest 中正确执行此操作的答案,请参阅stackoverflow.com/a/56565849/1248209
2021-05-04 17:21:10
我不认为这是一个好习惯。它不使用 Jest 或任何其他框架来模拟该方法,您将需要额外的努力来恢复该方法。
2021-05-05 17:21:10

一直在问类似的问题,我认为找到了解决方案。无论在何处实际使用 Person 类实例,这都应该有效。

const Person = require("../Person");

jest.mock("../Person", function () {
    const { default: mockRealPerson } = jest.requireActual('../Person');

    mockRealPerson.prototype.sayMyName = function () {
        return "Hello";
    }    

    return mockRealPerson
});

test('MyTest', () => {
    const person = new Person();
    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");
});

你可以像这样扩展它,而不是嘲笑这个类:

class MockedPerson extends Person {
  sayMyName () {
    return 'Hello'
  }
}
// and then
let person = new MockedPerson();
卓见!我发现它对于模拟类非常有用!❤️
2021-05-19 17:21:10

如果您使用的是 Typescript,则可以执行以下操作:

Person.prototype.sayMyName = jest.fn().mockImplementationOnce(async () => 
        await 'my name is dev'
);

在您的测试中,您可以执行以下操作:

const person = new Person();
const res = await person.sayMyName();
expect(res).toEqual('my name is dev');

希望这对某人有帮助!

我们如何断言模拟被调用?
2021-04-22 17:21:10