Skip to content

Commit

Permalink
Add several methods to set up default behaviors. These methods can be…
Browse files Browse the repository at this point in the history
… placed anywhere in the mocking set up, including after other calledWith setups, unlike previous "default" behavior using `when(fn).mockReturnValue(z)`. You can now do: `when(fn).calledWith(x).mockReturnValue(y).defaultReturnValue(z)`.
  • Loading branch information
timkindberg committed Dec 14, 2021
1 parent a6dc74d commit 77fe672
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 126 deletions.
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,23 @@ fn(2); // Will throw a helpful jest assertion error with args diff

#### Supports default behavior

Use any of `mockReturnValue`, `mockResolvedValue`, `mockRejectedValue`, `mockImplementation` directly on the object
Use any of `defaultReturnValue`, `defaultResolvedValue`, `defaultRejectedValue`, `defaultImplementation`
to set up a default behavior, which will serve as fallback if no matcher fits.

```javascript
when(fn)
.calledWith('foo').mockReturnValue('special')
.defaultReturnValue('default') // This line can be placed anywhere, doesn't have to be at the end

expect(fn('foo')).toEqual('special')
expect(fn('bar')).toEqual('default')
```

Or if you use any of `mockReturnValue`, `mockResolvedValue`, `mockRejectedValue`, `mockImplementation` directly on the object
before using `calledWith` it will also behave as a default fallback.

```javascript
// Same as above example
when(fn)
.mockReturnValue('default')
.calledWith('foo').mockReturnValue('special')
Expand All @@ -299,15 +312,15 @@ expect(fn('bar')).toEqual('default')
One idea is to set up a default implementation that throws an error if an improper call is made to the mock.

```javascript
// A default implementation that fails your test
const unsupportedCallError = (...args) => {
throw new Error(`Wrong args: ${JSON.stringify(args, null, 2)}`);
};

when(fn)
.mockImplementation(unsupportedCallError)
.calledWith(correctArgs)
.mockReturnValue(expectedValue);
.mockReturnValue(expectedValue)
.defaultImplementation(unsupportedCallError)

// A default implementation that fails your test
function unsupportedCallError(...args) {
throw new Error(`Wrong args: ${JSON.stringify(args, null, 2)}`);
}
```

#### Supports custom mockImplementation
Expand Down
26 changes: 17 additions & 9 deletions src/when.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ class WhenMock {
fn.__whenMock__ = this
this.callMocks = []
this._origMock = fn.getMockImplementation()
this.defaultImplementation = null
this._defaultImplementation = null

const _mockImplementation = (matchers, expectCall, once = false) => (mockImplementation) => {
if (matchers[0] === NO_CALLED_WITH_YET) {
this.defaultImplementation = mockImplementation
this._defaultImplementation = mockImplementation
}
// To enable dynamic replacement during a test:
// * call mocks with equal matchers are removed
Expand Down Expand Up @@ -108,8 +108,8 @@ class WhenMock {
}
}

if (this.defaultImplementation) {
return this.defaultImplementation(...args)
if (this._defaultImplementation) {
return this._defaultImplementation(...args)
}
if (typeof fn.__whenMock__._origMock === 'function') {
return fn.__whenMock__._origMock(...args)
Expand All @@ -131,18 +131,26 @@ class WhenMock {
mockRejectedValue: err => _mockImplementation(matchers, expectCall)(() => Promise.reject(err)),
mockRejectedValueOnce: err => _mockImplementation(matchers, expectCall, true)(() => Promise.reject(err)),
mockImplementation: implementation => _mockImplementation(matchers, expectCall)(implementation),
mockImplementationOnce: implementation => _mockImplementation(matchers, expectCall, true)(implementation)
mockImplementationOnce: implementation => _mockImplementation(matchers, expectCall, true)(implementation),
defaultImplementation: implementation => this.defaultImplementation(implementation),
defaultReturnValue: returnValue => this.defaultReturnValue(returnValue),
defaultResolvedValue: returnValue => this.defaultResolvedValue(returnValue),
defaultRejectedValue: err => this.defaultRejectedValue(err)
})

// These four functions are only used when the dev has not used `.calledWith` before calling one of the mock return functions
this.mockImplementation = mockImplementation => {
this.defaultImplementation = mockImplementation => {
// Set up an implementation with a special matcher that can never be matched because it uses a private symbol
// Additionally the symbols existence can be checked to see if a calledWith was omitted.
return _mockImplementation([NO_CALLED_WITH_YET], false)(mockImplementation)
}
this.mockReturnValue = returnValue => this.mockImplementation(() => returnValue)
this.mockResolvedValue = returnValue => this.mockReturnValue(Promise.resolve(returnValue))
this.mockRejectedValue = err => this.mockReturnValue(Promise.reject(err))
this.defaultReturnValue = returnValue => this.defaultImplementation(() => returnValue)
this.defaultResolvedValue = returnValue => this.defaultReturnValue(Promise.resolve(returnValue))
this.defaultRejectedValue = err => this.defaultResolvedValue(Promise.reject(err))
this.mockImplementation = this.defaultImplementation
this.mockReturnValue = this.defaultReturnValue
this.mockResolvedValue = this.defaultResolvedValue
this.mockRejectedValue = this.defaultRejectedValue

this.calledWith = (...matchers) => ({ ...mockFunctions(matchers, false) })
this.expectCalledWith = (...matchers) => ({ ...mockFunctions(matchers, true) })
Expand Down
Loading

0 comments on commit 77fe672

Please sign in to comment.