Skip to content

Commit

Permalink
Fix mockReturnValue overriding mockImplementationOnce (#8398)
Browse files Browse the repository at this point in the history
  • Loading branch information
grosto authored and SimenB committed Aug 22, 2019
1 parent 1d245fd commit 7b8393e
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@
- `[expect]` Display expectedDiff more carefully in toBeCloseTo ([#8389](https://github.com/facebook/jest/pull/8389))
- `[jest-fake-timers]` `getTimerCount` will not include cancelled immediates ([#8764](https://github.com/facebook/jest/pull/8764))
- `[jest-leak-detector]` [**BREAKING**] Use `weak-napi` instead of `weak` package ([#8686](https://github.com/facebook/jest/pull/8686))
- `[jest-mock]` Fix for mockReturnValue overriding mockImplementationOnce ([#8398](https://github.com/facebook/jest/pull/8398))
- `[jest-snapshot]` Remove only the added newlines in multiline snapshots ([#8859](https://github.com/facebook/jest/pull/8859))

### Chore & Maintenance
Expand Down
9 changes: 9 additions & 0 deletions packages/jest-mock/src/__tests__/index.test.ts
Expand Up @@ -1020,6 +1020,15 @@ describe('moduleMocker', () => {
});
});

test('mockReturnValue does not override mockImplementationOnce', () => {
const mockFn = jest
.fn()
.mockReturnValue(1)
.mockImplementationOnce(() => 2);
expect(mockFn()).toBe(2);
expect(mockFn()).toBe(1);
});

test('mockImplementation resets the mock', () => {
const fn = jest.fn();
expect(fn()).toBeUndefined();
Expand Down
59 changes: 13 additions & 46 deletions packages/jest-mock/src/index.ts
Expand Up @@ -71,8 +71,6 @@ type MockFunctionState<T, Y extends Array<unknown>> = {
};

type MockFunctionConfig = {
isReturnValueLastSet: boolean;
defaultReturnValue: unknown;
mockImpl: Function | undefined;
mockName: string;
specificReturnValues: Array<unknown>;
Expand Down Expand Up @@ -456,8 +454,6 @@ class ModuleMockerClass {

private _defaultMockConfig(): MockFunctionConfig {
return {
defaultReturnValue: undefined,
isReturnValueLastSet: false,
mockImpl: undefined,
mockName: 'jest.fn()',
specificMockImpls: [],
Expand Down Expand Up @@ -586,40 +582,21 @@ class ModuleMockerClass {
return mockImpl && mockImpl.apply(this, arguments);
}

const returnValue = mockConfig.defaultReturnValue;
// If return value is last set, either specific or default, i.e.
// mockReturnValueOnce()/mockReturnValue() is called and no
// mockImplementationOnce()/mockImplementation() is called after
// that.
// use the set return value.
if (mockConfig.specificReturnValues.length) {
return mockConfig.specificReturnValues.shift();
}

if (mockConfig.isReturnValueLastSet) {
return mockConfig.defaultReturnValue;
}

// If mockImplementationOnce()/mockImplementation() is last set,
// or specific return values are used up, use the mock
// implementation.
let specificMockImpl;
if (returnValue === undefined) {
specificMockImpl = mockConfig.specificMockImpls.shift();
if (specificMockImpl === undefined) {
specificMockImpl = mockConfig.mockImpl;
}
if (specificMockImpl) {
return specificMockImpl.apply(this, arguments);
}
// implementation use the mock
let specificMockImpl = mockConfig.specificMockImpls.shift();
if (specificMockImpl === undefined) {
specificMockImpl = mockConfig.mockImpl;
}
if (specificMockImpl) {
return specificMockImpl.apply(this, arguments);
}

// Otherwise use prototype implementation
if (returnValue === undefined && f._protoImpl) {
if (f._protoImpl) {
return f._protoImpl.apply(this, arguments);
}

return returnValue;
return undefined;
})();
} catch (error) {
// Store the thrown error so we can record it, then re-throw it.
Expand Down Expand Up @@ -675,26 +652,19 @@ class ModuleMockerClass {
return restore ? restore() : undefined;
};

f.mockReturnValueOnce = (value: T) => {
f.mockReturnValueOnce = (value: T) =>
// next function call will return this value or default return value
const mockConfig = this._ensureMockConfig(f);
mockConfig.specificReturnValues.push(value);
return f;
};
f.mockImplementationOnce(() => value);

f.mockResolvedValueOnce = (value: T) =>
f.mockImplementationOnce(() => Promise.resolve(value));

f.mockRejectedValueOnce = (value: T) =>
f.mockImplementationOnce(() => Promise.reject(value));

f.mockReturnValue = (value: T) => {
f.mockReturnValue = (value: T) =>
// next function call will return specified return value or this one
const mockConfig = this._ensureMockConfig(f);
mockConfig.isReturnValueLastSet = true;
mockConfig.defaultReturnValue = value;
return f;
};
f.mockImplementation(() => value);

f.mockResolvedValue = (value: T) =>
f.mockImplementation(() => Promise.resolve(value));
Expand All @@ -708,7 +678,6 @@ class ModuleMockerClass {
// next function call will use this mock implementation return value
// or default mock implementation return value
const mockConfig = this._ensureMockConfig(f);
mockConfig.isReturnValueLastSet = false;
mockConfig.specificMockImpls.push(fn);
return f;
};
Expand All @@ -718,8 +687,6 @@ class ModuleMockerClass {
): Mock<T, Y> => {
// next function call will use mock implementation return value
const mockConfig = this._ensureMockConfig(f);
mockConfig.isReturnValueLastSet = false;
mockConfig.defaultReturnValue = undefined;
mockConfig.mockImpl = fn;
return f;
};
Expand Down

0 comments on commit 7b8393e

Please sign in to comment.