96

In Sinon I can do the following:

var myObj = {
    prop: 'foo'
};

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

myObj.prop; // 'bar'

But how can I do the same with Jest? I can't just overwrite the function with something like jest.fn(), because it won't replace the getter

"can't set the value of get"

I_like_foxes
  • 1,179
  • 1
  • 7
  • 12

6 Answers6

188

For anyone else stumbling across this answer, Jest 22.1.0 introduced the ability to spy on getter and setter methods.

Edit: like in scieslak's answer below, because you can spy on getter and setter methods, you can use Jest mocks with them, just like with any other function:

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

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

expect(something).toEqual('bar')
Eduardo Russo
  • 4,121
  • 2
  • 22
  • 38
Franey
  • 4,164
  • 2
  • 16
  • 18
  • 2
    @taystack Being able to spy on the getters/setters was a necessary step toward being able to mock them like a regular function. I updated my answer to be more explicit about that and added a mock to the example code. – Franey Apr 02 '19 at 18:58
  • 5
    **`something property does not exist`** with `jest@24.9.0`with `["module:metro-react-native-babel-preset"`, while [it works fine with scieslak solution](https://stackoverflow.com/a/54346703/7295772) which sets the spy exactly on the `awesomeness` instance. In `rspec` I would use a method called `any_instance` and I would mock/stub `any_instance` of that class. There is a clear difference in syntax between the [jest official `spyOn` documentation](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname-accesstype) and your example. In that example jest spys on an object literal – Fabrizio Bertoglio Dec 23 '19 at 15:05
  • `const video = { get play() { return true; },};` – Fabrizio Bertoglio Dec 23 '19 at 15:06
  • 17
    @fabrizo jest.spyOn(MyClass, 'something', 'get').mockReturnValue('bar') should be jest.spyOn(MyClass.prototype, 'something', 'get').mockReturnValue('bar') – Mike P. Apr 10 '20 at 00:38
  • I Second @Mike P., you need to use the `prototype` for it to work – Eduardo Russo Aug 09 '22 at 13:38
84

You could use Object.defineProperty

Object.defineProperty(myObj, 'prop', {
  get: jest.fn(() => 'bar'),
  set: jest.fn()
});
Alex Robertson
  • 1,421
  • 10
  • 14
  • 3
    Thank you a lot, that works. Sometimes I notice I don't need more knowledge about some library/framework but the fundamental workings of JavaScript. May I ask one follow up question: How would I mock this function if I wanted my mock to replace the original function just one time but the next call should handled by the original function again? – I_like_foxes May 04 '17 at 07:03
  • This works, but when I try to do it in my beforeEach() call it fails with `Cannot redefine property`. Why is this? – b.lyte Nov 29 '17 at 21:51
  • 4
    @clu add `configurable: true` to your the `defineProperty` function. That should do the trick – cgat Dec 15 '17 at 00:42
  • 10
    Notice that - in contrast with mocks - it won't "unmock" your getter at the end of each test case, so - if other tests rely on the original return value of the getter - you may get different results running one test and the whole suite. – Misu Jul 24 '18 at 05:33
  • getting cannot redefine property. – Sandeep vashisth Jan 12 '19 at 13:46
  • 3
    Scroll down to Franey answer. It is more recent. – Tomasz Kula Jul 02 '19 at 09:05
  • This is really good as this allows working with the complex object's fixture - its POJO representation. This especially useful when you have to supply that object as dependency and would not like to bother creating the actual object - actually keeping yourself away from integration tesing when it is not yet desirable. – Valentine Shi Dec 25 '21 at 10:27
23

If you care about spying only, go for @Franey 's answer. However if you actually need to stub a value for the getter, this is how you can do it:

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)
  })
})
thomaux
  • 19,133
  • 10
  • 76
  • 103
scieslak
  • 584
  • 6
  • 8
  • 2
    I got an error that the property does not exist. It also looks like the other answer calls `spyOn` on the class whereas you call it on an instance. – Ran Lottem Mar 26 '20 at 10:35
  • 4
    I'm seeing the same thing - 'property does not exist' - when calling on an instance. – mdhvn May 25 '20 at 09:05
2

In my unity test case, as it is expected to mock external dependencies, following the thomaux's answer, i had to use a real instance instead of a mocked one in beforeEach function, see the snippet bellow.

//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')
  ...
)
Paulo Ricardo
  • 69
  • 1
  • 3
1

Since Jest v29.5 you can use jest.replaceProperty like this:

var myObj = {
    prop: 'foo'
};

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

myObj.prop; // 'bar'
Florian Ludewig
  • 4,338
  • 11
  • 71
  • 137
0

I usually use @Franey's answer with spyOn, but if you are mocking the the whole module you can just set it as a normal parameter:

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')
})

This is useful for some specific cases, but I thought it was worth mentioning.

Eduardo Russo
  • 4,121
  • 2
  • 22
  • 38