149

Jasmine's spyOn is good to change a method's behavior, but is there any way to change a value property (rather than a method) for an object? the code could be like below:

spyOn(myObj, 'valueA').andReturn(1);
expect(myObj.valueA).toBe(1);
Shuping
  • 5,388
  • 6
  • 43
  • 66

10 Answers10

132

In February 2017, they merged a PR adding this feature, they released in April 2017.

so to spy on getters/setters you use: const spy = spyOnProperty(myObj, 'myGetterName', 'get'); where myObj is your instance, 'myGetterName' is the name of that one defined in your class as get myGetterName() {} and the third param is the type get or set.

You can use the same assertions that you already use with the spies created with spyOn.

So you can for example:

const spy = spyOnProperty(myObj, 'myGetterName', 'get'); // to stub and return nothing. Just spy and stub.
const spy = spyOnProperty(myObj, 'myGetterName', 'get').and.returnValue(1); // to stub and return 1 or any value as needed.
const spy = spyOnProperty(myObj, 'myGetterName', 'get').and.callThrough(); // Call the real thing.

Here's the line in the github source code where this method is available if you are interested.

https://github.com/jasmine/jasmine/blob/7f8f2b5e7a7af70d7f6b629331eb6fe0a7cb9279/src/core/requireInterface.js#L199

And the spyOnProperty method is here

Answering the original question, with jasmine 2.6.1, you would:

const spy = spyOnProperty(myObj, 'valueA', 'get').andReturn(1);
expect(myObj.valueA).toBe(1);
expect(spy).toHaveBeenCalled();
Danny
  • 3,982
  • 1
  • 34
  • 42
Juan
  • 6,125
  • 3
  • 30
  • 31
  • 12
    How do I do this if `valueA` is a `Observable` or `Subject`? I'm getting `Property valueA does not have access type get` – Ka Mok Feb 28 '18 at 16:21
  • 5
    That probably means you can't use it on that property. spyOnProperty is using getOwnPropertyDescriptor and checking if the access type get|set for that property descriptor exists. The error you are getting is because the property descriptor is not set or get for the property you are trying to spy on. Jasmine does this: const descriptor = Object.getOwnPropertyDescriptor ... if(!descriptor[accessType]) { // error is thrown } – Juan Feb 28 '18 at 16:25
  • 1
    So, there is no way to spy a property without explicit getter and setter methods? To spy is a property used. – Domske Feb 05 '20 at 17:37
  • @Dominik Here I wrote all the under the hood stuff related to this in a longer article: https://medium.com/@juanlizarazo/how-to-spy-on-a-property-getter-or-setter-with-jasmine-ad06c00ba612 – Juan Feb 07 '20 at 06:00
  • Is this possible in Jest too? – Felix Feb 26 '20 at 09:19
  • Sorry but it's not clear for me how this is a test if you explicitly cast the output to be 1 and test it to be 1? – Mathieu Apr 03 '20 at 21:56
  • 1
    @Mathieu it is not a good assertion because it just asserts what we already know from what we told the spy to do, but it is just what they were asking and their very same question code snippet ¯\_(ツ)_/¯ The point of their question was how to spy on a property and not so much their specific example. – Juan Apr 03 '20 at 23:34
  • @Juan, I was looking for that kind of test. I'm desperatly trying to read a getter in my Jasmine test and it always return undefined... – Mathieu Apr 05 '20 at 18:22
  • @Mathieu that's the correct way to mock it. Here just the example was that you would not assert on the spy itself, as that's silly. You would assert something affected by your spy, some outcome or that the spy itself is called. For your specific issue, as undefined might be caused by something else, just ask / open a new question! someone will help :) – Juan Apr 09 '20 at 04:03
  • @Dominik - new to jasmine but I got something that can help here to spy a property that doesn't have getter/setter. you need to create spy of that as below: let service = { ...jasmine.createSpyObj('Service' , ['method']), PropertyWithoutGetterSetterORBehaviorObject : of(passSpiedValueHere)}; – Happy Porwal Oct 28 '22 at 18:09
  • @HappyPorwal Thanks, but I don't use Jasmine anymore. 2 years passed. I switched to Jest (sometimes with ts-mockito). This also works with Angular. I can recommend it. – Domske Oct 28 '22 at 19:45
13

Any reason you cannot just change it on the object directly? It is not as if javascript enforces visibility of a property on an object.

Paul Harris
  • 5,769
  • 1
  • 25
  • 41
  • 4
    using `spyOn` explicitly indicates that I want mock something, while I directly set the property implicitly indicates that I want mock something, and I am not sure somebody else will understand that I am mocking something when he is reading the code. Other case is that I don't want change the inner behavior of the object, for example if I change the length property for an array, the array is trimmed, so a mock will be better – Shuping Jan 03 '14 at 05:25
  • @Shuping, in this case, you'd not be mocking. You'd be stubbing which is perfectly okay. You'd be "changing the behaviour" only inside the the test which is what you were trying to achieve with the `spyOn`. – Fabio Milheiro Aug 14 '14 at 08:13
  • Possibly you want to spy on the runtime object prototype to make sure the property will exist at runtime. Jasmine `spyOn` fails the test if the property does not exist. – sennett Apr 02 '15 at 07:46
  • 3
    One example is to set or delete window.sessionStorage: `TypeError: Cannot assign to read only property 'sessionStorage' of object '#'` – Chris Sattinger Jun 08 '16 at 13:20
  • This is the right answer. You should be able to do this: `myObj.valueA = 1;` – Tom Dec 21 '16 at 08:25
  • 2
    It is not always possible to reassign an object property in javascript – Renaud Jul 12 '18 at 09:27
  • @Paul Harris, What if the value is of type observable? if i want to return observable with value, then? – pradeep gowda Oct 05 '18 at 03:08
13

The best way is to use spyOnProperty. It expects 3 parameters and you need to pass get or set as a third param.

Example

const div = fixture.debugElement.query(By.css('.ellipsis-overflow'));
// now mock properties
spyOnProperty(div.nativeElement, 'clientWidth', 'get').and.returnValue(1400);
spyOnProperty(div.nativeElement, 'scrollWidth', 'get').and.returnValue(2400);

Here I am setting the get of clientWidth of div.nativeElement object.

Community
  • 1
  • 1
Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
12

Jasmine doesn't have that functionality, but you might be able to hack something together using Object.defineProperty.

You could refactor your code to use a getter function, then spy on the getter.

spyOn(myObj, 'getValueA').andReturn(1);
expect(myObj.getValueA()).toBe(1);
Eric
  • 6,965
  • 26
  • 32
8

The right way to do this is with the spy on property, it will allow you to simulate a property on an object with an specific value.

const spy = spyOnProperty(myObj, 'valueA').and.returnValue(1);
expect(myObj.valueA).toBe(1);
expect(spy).toHaveBeenCalled();
alexalejandroem
  • 1,094
  • 12
  • 17
6

If you are using ES6 (Babel) or TypeScript you can stub out the property using get and set accessors

export class SomeClassStub {
  getValueA = jasmine.createSpy('getValueA');
  setValueA = jasmine.createSpy('setValueA');
  get valueA() { return this.getValueA(); }
  set valueA(value) { this.setValueA(value); }
}

Then in your test you can check that the property is set with:

stub.valueA = 'foo';

expect(stub.setValueA).toHaveBeenCalledWith('foo');
Martin
  • 15,820
  • 4
  • 47
  • 56
  • Or, if the getters are part of the class under test, the stubs could be injected in a subclass. – ccprog Apr 01 '18 at 19:18
1

Suppose there is a method like this that needs testing The src property of the tiny image needs checking

function reportABCEvent(cat, type, val) {
                var i1 = new Image(1, 1);
                var link = getABC('creosote');
                    link += "&category=" + String(cat);
                    link += "&event_type=" + String(type);
                    link += "&event_value=" + String(val);
                    i1.src = link;
                }

The spyOn() below causes the "new Image" to be fed the fake code from the test the spyOn code returns an object that only has a src property

As the variable "hook" is scoped to be visible in the fake code in the SpyOn and also later after the "reportABCEvent" is called

describe("Alphabetic.ads", function() {
    it("ABC events create an image request", function() {
    var hook={};
    spyOn(window, 'Image').andCallFake( function(x,y) {
          hook={ src: {} }
          return hook;
      }
      );
      reportABCEvent('testa', 'testb', 'testc');
      expect(hook.src).
      toEqual('[zubzub]&arg1=testa&arg2=testb&event_value=testc');
    });

This is for jasmine 1.3 but might work on 2.0 if the "andCallFake" is altered to the 2.0 name

Vorsprung
  • 32,923
  • 5
  • 39
  • 63
1

I'm using a kendo grid and therefore can't change the implementation to a getter method but I want to test around this (mocking the grid) and not test the grid itself. I was using a spy object but this doesn't support property mocking so I do this:

    this.$scope.ticketsGrid = { 
        showColumn: jasmine.createSpy('showColumn'),
        hideColumn: jasmine.createSpy('hideColumn'),
        select: jasmine.createSpy('select'),
        dataItem: jasmine.createSpy('dataItem'),
        _data: []
    } 

It's a bit long winded but it works a treat

jolySoft
  • 2,948
  • 2
  • 30
  • 34
1

You can also use jasmin.creatSpyObj('ObjectName', [methodNames...], {prop1:propvalue, prop2:provalue2})

Ron Jonk
  • 706
  • 6
  • 16
0

I'm a bit late to the party here i know but,

You could directly access the calls object, which can give you the variables for each call

expect(spy.calls.argsFor(0)[0].value).toBe(expectedValue)
CosmicChild
  • 181
  • 8