Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(jest-mock): clear mock when jest.restoreAllMocks() is called #13867

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,8 @@

### Fixes

- `[jest-mock]` Clear mock state when `jest.restoreAllMocks()` is called ([#13867](https://github.com/facebook/jest/pull/13867))

### Chore & Maintenance

### Performance
Expand Down
130 changes: 111 additions & 19 deletions packages/jest-mock/src/__tests__/index.test.ts
Expand Up @@ -1312,6 +1312,36 @@ describe('moduleMocker', () => {
);
});

it('supports restoring a spy', () => {
let methodOneCalls = 0;
const obj = {
methodOne() {
methodOneCalls++;
},
};

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

obj.methodOne();

// The spy and the original function got called.
expect(methodOneCalls).toBe(1);
expect(spy1.mock.calls).toHaveLength(1);

expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(true);

spy1.mockRestore();

// After restoring the spy, the method is not mock function.
expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(false);

obj.methodOne();

// After restoring the spy only the real method bumps its call count, not the spy.
expect(methodOneCalls).toBe(2);
expect(spy1.mock.calls).toHaveLength(0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling mockRestore() the state is reset. This is passing on main as well.

});

it('supports restoring all spies', () => {
let methodOneCalls = 0;
let methodTwoCalls = 0;
Expand All @@ -1327,25 +1357,32 @@ describe('moduleMocker', () => {
const spy1 = moduleMocker.spyOn(obj, 'methodOne');
const spy2 = moduleMocker.spyOn(obj, 'methodTwo');

// First, we call with the spies: both spies and both original functions
// should be called.
obj.methodOne();
obj.methodTwo();

// Both spies and both original functions got called.
expect(methodOneCalls).toBe(1);
expect(methodTwoCalls).toBe(1);
expect(spy1.mock.calls).toHaveLength(1);
expect(spy2.mock.calls).toHaveLength(1);

expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(true);
expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(true);

moduleMocker.restoreAllMocks();

// Then, after resetting all mocks, we call methods again. Only the real
// methods should bump their count, not the spies.
// After restoring all mocks, the methods are not mock functions.
expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(false);
expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(false);

obj.methodOne();
obj.methodTwo();

// After restoring all mocks only the real methods bump their count, not the spies.
expect(methodOneCalls).toBe(2);
expect(methodTwoCalls).toBe(2);
expect(spy1.mock.calls).toHaveLength(1);
expect(spy2.mock.calls).toHaveLength(1);
expect(spy1.mock.calls).toHaveLength(0);
expect(spy2.mock.calls).toHaveLength(0);
Comment on lines -1347 to +1385
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But after calling restoreAllMocks() the old state persist. At first I thought that 1 is result of second call of the spied method, but it is not a spy anymore. See isMockFunction() assertion above.

So this is fixed.

});

it('should work with getters', () => {
Expand Down Expand Up @@ -1482,6 +1519,33 @@ describe('moduleMocker', () => {
);
});

it('supports restoring a spy', () => {
let methodOneCalls = 0;
const obj = {
get methodOne() {
return function () {
methodOneCalls++;
};
},
};

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

obj.methodOne();

// The spy and the original function are called.
expect(methodOneCalls).toBe(1);
expect(spy1.mock.calls).toHaveLength(1);

spy1.mockRestore();

obj.methodOne();

// After restoring the spy only the real method bumps its call count, not the spy.
expect(methodOneCalls).toBe(2);
expect(spy1.mock.calls).toHaveLength(0);
});

it('supports restoring all spies', () => {
let methodOneCalls = 0;
let methodTwoCalls = 0;
Expand All @@ -1501,25 +1565,25 @@ describe('moduleMocker', () => {
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();

// Both spies and both original functions got called.
expect(methodOneCalls).toBe(1);
expect(methodTwoCalls).toBe(1);
expect(spy1.mock.calls).toHaveLength(1);
expect(spy2.mock.calls).toHaveLength(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();

// After restoring all mocks only the real methods bump their count, not the spies.
expect(methodOneCalls).toBe(2);
expect(methodTwoCalls).toBe(2);
expect(spy1.mock.calls).toHaveLength(1);
expect(spy2.mock.calls).toHaveLength(1);
expect(spy1.mock.calls).toHaveLength(0);
expect(spy2.mock.calls).toHaveLength(0);
});

it('should work with getters on the prototype chain', () => {
Expand Down Expand Up @@ -1587,6 +1651,34 @@ describe('moduleMocker', () => {
expect(obj.property).toBe(true);
});

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

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

obj.methodOne();

// The spy and the original function are called.
expect(methodOneCalls).toBe(1);
expect(spy1.mock.calls).toHaveLength(1);

spy1.mockRestore();

obj.methodOne();

// After restoring the spy only the real method bumps its call count, not the spy.
expect(methodOneCalls).toBe(2);
expect(spy1.mock.calls).toHaveLength(0);
});

it('supports restoring all spies on the prototype chain', () => {
let methodOneCalls = 0;
let methodTwoCalls = 0;
Expand All @@ -1607,25 +1699,25 @@ describe('moduleMocker', () => {
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();

// Both spies and both original functions got called.
expect(methodOneCalls).toBe(1);
expect(methodTwoCalls).toBe(1);
expect(spy1.mock.calls).toHaveLength(1);
expect(spy2.mock.calls).toHaveLength(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();

// After restoring all mocks only the real methods bump their count, not the spies.
expect(methodOneCalls).toBe(2);
expect(methodTwoCalls).toBe(2);
expect(spy1.mock.calls).toHaveLength(1);
expect(spy2.mock.calls).toHaveLength(1);
expect(spy1.mock.calls).toHaveLength(0);
expect(spy2.mock.calls).toHaveLength(0);
});
});

Expand Down Expand Up @@ -1664,7 +1756,7 @@ describe('moduleMocker', () => {
expect(obj.property).toBe(1);
});

it('should allow mocking with value of different value', () => {
it('should allow mocking with value of different type', () => {
const obj = {
property: 1,
};
Expand Down
1 change: 1 addition & 0 deletions packages/jest-mock/src/index.ts
Expand Up @@ -1414,6 +1414,7 @@ export class ModuleMocker {
}

restoreAllMocks(): void {
this._mockState = new WeakMap();
this._spyState.forEach(restore => restore());
this._spyState = new Set();
}
Expand Down