Skip to content

Commit

Permalink
feat: update spyOnProperty method, fixes #… (#5753)
Browse files Browse the repository at this point in the history
* feat: update spyOnProperty method, fixes #5746

* changelog update for spyOnProperty, #5753
  • Loading branch information
thomasjinlo authored and cpojer committed Mar 11, 2018
1 parent 2a6fc70 commit 4034718
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
([#5670](https://github.com/facebook/jest/pull/5670))
* `[expect]` Add inverse matchers (`expect.not.arrayContaining`, etc.,
[#5517](https://github.com/facebook/jest/pull/5517))
* `[jest-mock]` Update `spyOnProperty` to support spying on the prototype chain
([#5753](https://github.com/facebook/jest/pull/5753))
* `[jest-mock]` Add tracking of return values in the `mock` property
([#5752](https://github.com/facebook/jest/pull/5752))
* `[jest-mock]` Add tracking of thrown errors in the `mock` property
Expand Down
107 changes: 107 additions & 0 deletions packages/jest-mock/src/__tests__/jest_mock.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -900,5 +900,112 @@ describe('moduleMocker', () => {
expect(spy1.mock.calls.length).toBe(1);
expect(spy2.mock.calls.length).toBe(1);
});

it('should work with getters on the prototype chain', () => {
let isOriginalCalled = false;
let originalCallThis;
let originalCallArguments;
const prototype = {
get method() {
return function() {
isOriginalCalled = true;
originalCallThis = this;
originalCallArguments = arguments;
};
},
};
const obj = Object.create(prototype, {});

const spy = moduleMocker.spyOn(obj, 'method', 'get');

const thisArg = {this: true};
const firstArg = {first: true};
const secondArg = {second: true};
obj.method.call(thisArg, firstArg, secondArg);
expect(isOriginalCalled).toBe(true);
expect(originalCallThis).toBe(thisArg);
expect(originalCallArguments.length).toBe(2);
expect(originalCallArguments[0]).toBe(firstArg);
expect(originalCallArguments[1]).toBe(secondArg);
expect(spy).toHaveBeenCalled();

isOriginalCalled = false;
originalCallThis = null;
originalCallArguments = null;
spy.mockReset();
spy.mockRestore();
obj.method.call(thisArg, firstArg, secondArg);
expect(isOriginalCalled).toBe(true);
expect(originalCallThis).toBe(thisArg);
expect(originalCallArguments.length).toBe(2);
expect(originalCallArguments[0]).toBe(firstArg);
expect(originalCallArguments[1]).toBe(secondArg);
expect(spy).not.toHaveBeenCalled();
});

test('should work with setters on the prototype chain', () => {
const prototype = {
_property: false,
set property(value) {
this._property = value;
},
get property() {
return this._property;
},
};
const obj = Object.create(prototype, {});

const spy = moduleMocker.spyOn(obj, 'property', 'set');
obj.property = true;
expect(spy).toHaveBeenCalled();
expect(obj.property).toBe(true);
obj.property = false;
spy.mockReset();
spy.mockRestore();
obj.property = true;
expect(spy).not.toHaveBeenCalled();
expect(obj.property).toBe(true);
});

it('supports restoring all spies on the prototype chain', () => {
let methodOneCalls = 0;
let methodTwoCalls = 0;
const prototype = {
get methodOne() {
return function() {
methodOneCalls++;
};
},
get methodTwo() {
return function() {
methodTwoCalls++;
};
},
};
const obj = Object.create(prototype, {});

const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get');
const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get');

// First, we call with the spies: both spies and both original functions
// should be called.
obj.methodOne();
obj.methodTwo();
expect(methodOneCalls).toBe(1);
expect(methodTwoCalls).toBe(1);
expect(spy1.mock.calls.length).toBe(1);
expect(spy2.mock.calls.length).toBe(1);

moduleMocker.restoreAllMocks();

// Then, after resetting all mocks, we call methods again. Only the real
// methods should bump their count, not the spies.
obj.methodOne();
obj.methodTwo();
expect(methodOneCalls).toBe(2);
expect(methodTwoCalls).toBe(2);
expect(spy1.mock.calls.length).toBe(1);
expect(spy2.mock.calls.length).toBe(1);
});
});
});
8 changes: 7 additions & 1 deletion packages/jest-mock/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,13 @@ class ModuleMockerClass {
throw new Error('No property name supplied');
}

const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
let proto = Object.getPrototypeOf(obj);

while (!descriptor && proto !== null) {
descriptor = Object.getOwnPropertyDescriptor(proto, propertyName);
proto = Object.getPrototypeOf(proto);
}

if (!descriptor) {
throw new Error(propertyName + ' property does not exist');
Expand Down

0 comments on commit 4034718

Please sign in to comment.