Skip to content

Commit

Permalink
Fix broken function args
Browse files Browse the repository at this point in the history
Function args were broken when I introduced the custom function matchers. So now function matchers must have a `._isFunctionMatcher` property set to `true`.

This is made easy by wrapping a regular function (not mock or spy) with `when`.

```js
when(fn)
    .calledWith(when(allKeysAreSingleLetters)) <-- Like This
```
  • Loading branch information
timkindberg committed Mar 7, 2021
1 parent fb0dbe6 commit 7175ece
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 16 deletions.
39 changes: 27 additions & 12 deletions src/when.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ const checkArgumentMatchers = (expectCall, args) => (match, matcher, i) => {
logger.debug(` matcher: ${String(matcher)}`)
logger.debug(` arg: ${String(arg)}`)

const isFunctionMatcher = typeof matcher === 'function' && matcher._isFunctionMatcher

// Assert the match for better messaging during a failure
if (expectCall) {
if (typeof matcher === 'function') {
if (isFunctionMatcher) {
const isMatch = matcher(arg)
const msg = `Failed function matcher within expectCalledWith: ${matcher.name}(${JSON.stringify(arg)}) did not return true\n\n\n...rest of the stack...`
assert.equal(isMatch, true, msg)
Expand All @@ -30,7 +32,7 @@ const checkArgumentMatchers = (expectCall, args) => (match, matcher, i) => {
}
}

if (typeof matcher === 'function') {
if (isFunctionMatcher) {
return matcher(arg)
}

Expand Down Expand Up @@ -124,17 +126,30 @@ class WhenMock {
}

const when = (fn) => {
if (fn.__whenMock__ instanceof WhenMock) return fn.__whenMock__
const whenMock = new WhenMock(fn)
registry.add(fn)
fn._origMockReset = fn.mockReset
fn.mockReset = () => {
resetWhenMocksOnFn(fn)
fn.mockReset = fn._origMockReset
fn._origMockReset = undefined
fn.mockReset()
// This bit is for when you use `when` to make a WhenMock
// when(fn) <-- This one
// .calledWith(when(numberIsGreaterThanZero)) <-- Not this one
if (fn._isMockFunction) {
if (fn.__whenMock__ instanceof WhenMock) return fn.__whenMock__
const whenMock = new WhenMock(fn)
registry.add(fn)
fn._origMockReset = fn.mockReset
fn.mockReset = () => {
resetWhenMocksOnFn(fn)
fn.mockReset = fn._origMockReset
fn._origMockReset = undefined
fn.mockReset()
}
return whenMock
}

// This bit is for when you use `when` as a function matcher
// when(fn) <-- Not this one
// .calledWith(when(numberIsGreaterThanZero)) <-- This one
if (typeof fn === 'function') {
fn._isFunctionMatcher = true
return fn
}
return whenMock
}

const resetAllWhenMocks = () => {
Expand Down
25 changes: 21 additions & 4 deletions src/when.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,19 @@ describe('When', () => {
const fn = jest.fn()

const anyString = expect.any(String)
const myFunction = function () {}

when(fn)
.calledWith(1, 'foo', true, anyString, undefined)
.calledWith(1, 'foo', true, anyString, undefined, myFunction)
.mockReturnValue('x')

expect(fn(1, 'foo', true, 'whatever', undefined)).toEqual('x')
expect(fn(1, 'foo', true, 'whatever', undefined, myFunction)).toEqual('x')
expect(spyEquals).toBeCalledWith(1, 1)
expect(spyEquals).toBeCalledWith('foo', 'foo')
expect(spyEquals).toBeCalledWith(true, true)
expect(spyEquals).toBeCalledWith('whatever', anyString)
expect(spyEquals).toBeCalledWith(undefined, undefined)
expect(spyEquals).toBeCalledWith(myFunction, myFunction)
})

it('only matches exact sets of args, too little or too many args do not trigger mock return', () => {
Expand All @@ -192,7 +194,7 @@ describe('When', () => {
const numberDivisibleBy3 = (arg) => arg % 3 === 0

when(fn)
.calledWith(allValuesTrue, numberDivisibleBy3)
.calledWith(when(allValuesTrue), when(numberDivisibleBy3))
.mockReturnValue('x')

expect(fn({ foo: true, bar: true }, 9)).toEqual('x')
Expand All @@ -207,14 +209,29 @@ describe('When', () => {
const numberDivisibleBy3 = (arg) => arg % 3 === 0

when(fn)
.expectCalledWith(allValuesTrue, numberDivisibleBy3)
.expectCalledWith(when(allValuesTrue), when(numberDivisibleBy3))
.mockReturnValue('x')

expect(fn({ foo: true, bar: true }, 9)).toEqual('x')
expect(() => fn({ foo: false, bar: true }, 9)).toThrow(/Failed function matcher within expectCalledWith: allValuesTrue\(\{"foo":false,"bar":true\}\) did not return true/)
expect(() => fn({ foo: true, bar: true }, 13)).toThrow(/Failed function matcher within expectCalledWith: numberDivisibleBy3\(13\) did not return true/)
})

it('does not call regular functions as function matchers', () => {
const fn = jest.fn()

const doNotCallMeBro = () => {
throw new Error('BOOM')
}

when(fn)
.expectCalledWith(doNotCallMeBro)
.mockReturnValue('x')

expect(fn(doNotCallMeBro)).toEqual('x')
expect(() => fn(doNotCallMeBro)).not.toThrow()
})

it('supports compound when declarations', () => {
const fn = jest.fn()

Expand Down

0 comments on commit 7175ece

Please sign in to comment.