如何使用Jest模拟/替换对象的getter函数?

97

在Sinon中,我可以进行以下操作:

var myObj = {
    prop: 'foo'
};

sinon.stub(myObj, 'prop').get(function getterFn() {
    return 'bar';
});

myObj.prop; // 'bar'

但是我该如何使用Jest做到同样的效果呢?我不能简单地用jest.fn()来覆盖该函数,因为它无法替换getter。

"无法设置get的值"

6个回答

190

对于其他人来说,Jest 22.1.0版本引入了一项功能,可以监视getter和setter方法

编辑:就像下面scieslak的答案中所述,因为你可以监视getter和setter方法,所以可以像任何其他函数一样使用Jest模拟它们:

class MyClass {
  get something() {
    return 'foo'
  }
}

jest.spyOn(MyClass.prototype, 'something', 'get').mockReturnValue('bar')
const something = new MyClass().something

expect(something).toEqual('bar')

2
@taystack 能够窥视getter/setter是实现像普通函数一样mock它们的必要步骤。我更新了我的答案,更明确地说明了这一点,并在示例代码中添加了一个mock。 - Franey
5
使用jest@24.9.0和["module:metro-react-native-babel-preset"]时出现了"something property does not exist"错误,而使用scieslak的解决方案,可以在awesomeness实例上准确设置spy。在rspec中,我会使用一个叫做any_instance的方法,并且会mock/stub该类的any_instance。在jest官方的spyOn文档中,语法与你的示例有明显的差别。在那个示例中,jest对一个对象字面量进行了spy。 - Fabrizio Bertoglio
const video = { get play() { return true; },}; - Fabrizio Bertoglio
17
应该翻译为:jest.spyOn(MyClass.prototype, 'something', 'get').mockReturnValue('bar')。 - Mike P.
我赞同 @Mike P. 的观点,你需要使用 prototype 才能使其正常工作。 - Eduardo Russo

85

您可以使用Object.defineProperty

Object.defineProperty(myObj, 'prop', {
  get: jest.fn(() => 'bar'),
  set: jest.fn()
});

3
非常感谢,那起作用了。有时候我注意到我不需要更多关于一些库/框架的知识,而是需要了解 JavaScript 的基本工作原理。我可以问一个后续问题吗: 如果我想要模拟这个函数,并且希望我的模拟只替换原始函数一次,但下一次调用应该再次由原始函数处理,我该怎么做? - I_like_foxes
4
请将 configurable: true 添加到你的 defineProperty 函数中。这样应该就可以解决问题了。 - cgat
10
请注意,与模拟对象不同的是,它不会在每个测试用例结束时“取消模拟”您的getter方法。因此,如果其他测试依赖于getter方法的原始返回值,则在运行单个测试和整个测试套件时可能会得到不同的结果。 - Misu
无法重新定义属性。 - Sandeep vashisth
3
向下滚动至 Franey 的回答,它是更新的。 - Tomasz Kula
显示剩余2条评论

23

如果你只关心间谍工作,那就使用 @Franey 的答案。但是,如果您实际上需要为 getter 存根一个值,这就是您可以执行的操作:

class Awesomeness {
  get isAwesome() {
    return true
  }
}

describe('Awesomeness', () => {
  it('is not always awesome', () => {
    const awesomeness = new Awesomeness
    jest.spyOn(awesomeness, 'isAwesome', 'get').mockReturnValue(false)

    expect(awesomeness.isAwesome).toEqual(false)
  })
})

3
我收到了一个错误,说该属性不存在。另外,看起来其他答案在类上调用了 spyOn,而你在实例上调用了它。 - Ran Lottem
5
在调用实例时,我看到了与“属性不存在”相同的提示信息。 - mdhvn

2
在我的Unity测试用例中,由于需要模拟外部依赖项,根据thomaux的回答,我不得不在beforeEach函数中使用真实实例而不是模拟实例,如下面的代码片段所示。
//declaration of MyExternalConfigService with getter
@Injectable()
export class MyExternalConfigService {
  constructor(private readonly configService: ConfigService) {}
  get myProp():string {
    return this.configService.get<string>('context.myProp')
  }
}

//beforeEach unit test configuration
beforeEach(async () => {
  const module: TestingModule = await Test.createTestingModule({
    providers: [
      MyServiceToBeTested,
      {
       provide: MyExternalConfigService,
        useValue: new MyExternalConfigService(new ConfigService())
      }        
    ]
  }).compile()

  service = module.get<MyServiceToBeTested>(
    MyServiceToBeTested
  )
  configService = module.get<MyExternalConfigService>MyExternalConfigService)

})

//mocking a value to your test case
it('my test case', ()=>{
  jest
    .spyOn(configService, 'myProp', 'get')
    .mockImplementationOnce(() => 'mockedValue')
  ...
)

2

自 Jest v29.5 版本起,您可以像这样使用 jest.replaceProperty

var myObj = {
    prop: 'foo'
};

jest.replaceProperty(myObj, 'prop', 'bar')

myObj.prop; // 'bar'

0

我通常使用@Franey的{{link1:spyOn答案}},但如果您要模拟整个模块,则可以将其设置为普通参数:

class MyClass {
  get something() {
    return 'foo'
  }
}

// With this here, you are ignoring the whole class, so the property will be set by whatever you set
jest.mock('./MyClass');

test('something', () => {
  MyClass.prototype.something = 'bar'
  const something = new MyClass().something

  expect(something).toEqual('bar')
})

这对于某些特定情况非常有用,但我认为值得一提。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接