Skip to content

Commit

Permalink
Allow defaults to be set up after calledWiths or even without any cal…
Browse files Browse the repository at this point in the history
…ledWiths
  • Loading branch information
timkindberg committed Sep 19, 2021
1 parent fe6dba9 commit cc6ca58
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 83 deletions.
37 changes: 23 additions & 14 deletions src/when.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,23 @@ const checkArgumentMatchers = (expectCall, args) => (match, matcher, i) => {

return utils.equals(arg, matcher)
}

const NO_CALLED_WITH_YET = Symbol('NO_CALLED_WITH')

class WhenMock {
constructor (fn, defaultImplementation = null) {
constructor (fn) {
// Incrementing ids assigned to each call mock to help with sorting as new mocks are added
this.nextCallMockId = 0
this.fn = fn
fn.__whenMock__ = this
this.callMocks = []
this._origMock = fn.getMockImplementation()

if (defaultImplementation) {
this.fn.mockImplementation(() => {
throw new Error('Unintended use: Only use default value in combination with .calledWith(..), ' +
'or use standard mocking without jest-when.')
})
}
this.defaultImplementation = null

const _mockImplementation = (matchers, expectCall, once = false) => (mockImplementation) => {
if (matchers[0] === NO_CALLED_WITH_YET) {
this.defaultImplementation = mockImplementation
}
// To enable dynamic replacement during a test:
// * call mocks with equal matchers are removed
// * `once` mocks are used prioritized
Expand Down Expand Up @@ -83,14 +83,19 @@ class WhenMock {
matchers.reduce(checkArgumentMatchers(expectCall, args), true)
}

if (isMatch) {
if (isMatch && typeof mockImplementation === 'function') {
this.callMocks[i].called = true
return mockImplementation(...args)
}
}
return defaultImplementation ? defaultImplementation(...args)
: (typeof fn.__whenMock__._origMock === 'function'
? fn.__whenMock__._origMock(...args) : undefined)

if (this.defaultImplementation) {
return this.defaultImplementation(...args)
}
if (typeof fn.__whenMock__._origMock === 'function') {
return fn.__whenMock__._origMock(...args)
}
return undefined
})

return {
Expand All @@ -110,13 +115,17 @@ class WhenMock {
mockImplementationOnce: implementation => _mockImplementation(matchers, expectCall, true)(implementation)
})

this.mockImplementation = mockImplementation => new WhenMock(fn, mockImplementation)
// These four functions are only used when the dev has not used `.calledWith` before calling one of the mock return functions
this.mockImplementation = 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.calledWith = (...matchers) => ({ ...mockFunctions(matchers, false) })

this.expectCalledWith = (...matchers) => ({ ...mockFunctions(matchers, true) })

this.resetWhenMocks = () => {
Expand Down
18 changes: 14 additions & 4 deletions src/when.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ describe('When', () => {
expect(fn('foo2')).toEqual('newDefault')
})

it('allows defining the default NOT in a chained case', () => {
it('allows defining the default NOT in a chained case', async () => {
const fn = jest.fn()

when(fn).mockRejectedValue(false)
Expand All @@ -749,7 +749,8 @@ describe('When', () => {
.calledWith(expect.anything())
.mockResolvedValue(true)

expect(fn()).rejects.toEqual(false)
await expect(fn('anything')).resolves.toEqual(true)
await expect(fn()).rejects.toEqual(false)
})

it('allows overriding the default NOT in a chained case', () => {
Expand All @@ -771,8 +772,7 @@ describe('When', () => {
when(fn)
.mockReturnValue('default')

expect(fn).toThrow('Unintended use: Only use default value in combination with .calledWith(..), ' +
'or use standard mocking without jest-when.')
expect(fn()).toEqual('default')
})

it('will not throw on old non-throwing case', () => {
Expand Down Expand Up @@ -844,5 +844,15 @@ describe('When', () => {
expect(fn(1)).toBe('mock')
expect(fn(2)).toBe('real')
})

it('keeps default mockReturnValue when not matched', () => {
const fn = jest.fn()

when(fn).calledWith(1).mockReturnValue('a')
when(fn).mockReturnValue('b')

expect(fn(1)).toEqual('a') // fails and will still return 'b' (as before my change)
expect(fn(2)).toEqual('b')
})
})
})
66 changes: 1 addition & 65 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1248,16 +1248,6 @@ builtins@^1.0.3:
resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og=

bunyan@^1.8.12:
version "1.8.12"
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797"
integrity sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=
optionalDependencies:
dtrace-provider "~0.8"
moment "^2.10.6"
mv "~2"
safe-json-stringify "~1"

cacheable-lookup@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38"
Expand Down Expand Up @@ -1827,13 +1817,6 @@ dot-prop@^6.0.1:
dependencies:
is-obj "^2.0.0"

dtrace-provider@~0.8:
version "0.8.7"
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.7.tgz#dc939b4d3e0620cfe0c1cd803d0d2d7ed04ffd04"
integrity sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=
dependencies:
nan "^2.10.0"

duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
Expand Down Expand Up @@ -2346,17 +2329,6 @@ glob-parent@^5.1.0:
dependencies:
is-glob "^4.0.1"

glob@^6.0.1:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"

glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
Expand Down Expand Up @@ -3898,7 +3870,7 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==

"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
Expand Down Expand Up @@ -3931,11 +3903,6 @@ mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies:
minimist "0.0.8"

moment@^2.10.6:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=

ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
Expand Down Expand Up @@ -3974,30 +3941,11 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==

mv@~2:
version "2.1.1"
resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2"
integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=
dependencies:
mkdirp "~0.5.1"
ncp "~2.0.0"
rimraf "~2.4.0"

nan@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==

natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=

ncp@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=

new-github-release-url@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/new-github-release-url/-/new-github-release-url-1.0.0.tgz#493847e6fecce39c247e9d89929be773d2e7f777"
Expand Down Expand Up @@ -4778,13 +4726,6 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"

rimraf@~2.4.0:
version "2.4.5"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da"
integrity sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=
dependencies:
glob "^6.0.1"

rimraf@~2.6.1:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
Expand Down Expand Up @@ -4840,11 +4781,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==

safe-json-stringify@~1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd"
integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==

"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
Expand Down

1 comment on commit cc6ca58

@AndrewSouthpaw
Copy link
Contributor

Choose a reason for hiding this comment

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

👏🏼 👏🏼 👏🏼 This solves a problem I just encountered over the weekend, thank you!

Please sign in to comment.