Skip to content

Releases: timkindberg/jest-when

Support @golevelup/ts-jest and jest-mock-extended proxies

07 Feb 23:39
Compare
Choose a tag to compare

v3.5.0...v3.5.1

Default Behavior Methods

14 Dec 19:53
Compare
Choose a tag to compare

New methods:

  • defaultReturnValue
  • defaultResolvedValue
  • defaultRejectedValue
  • defaultImplementation

Use them to set up a default behavior, which will serve as fallback if no matcher fits.

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')

Previously you needed to use any of mockReturnValue, mockResolvedValue, mockRejectedValue, or mockImplementation directly on the object before using calledWith to create a default fallback.

// Same as above example
when(fn)
  .mockReturnValue('default')
  .calledWith('foo').mockReturnValue('special')

One neat idea, and the reason I even did this work, is to set up a default implementation that throws an error if an improper call is made to the mock.

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

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

Commits:

  • Merge pull request #92 from timkindberg/default_methods 67b7532
  • Add several methods to set up default behaviors. These methods can be 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). 77fe672
  • Add documentation example for unsupported call error as default implementation a6dc74d

v3.4.2...v3.5.0

v3.4.2

28 Oct 01:24
Compare
Choose a tag to compare
  • Reset assertionCalls to zero so that our hack doesn't interfere with existing expect.assertions() usage 3a27d0b

v3.4.1...v3.4.2

v3.4.1

05 Oct 16:10
Compare
Choose a tag to compare
  • Access the equals util by grabbing it from the expect this arg. Temporary hack until core jest exposes this util officially. 5a8cc93

See jestjs/jest#11867 (comment)

v3.4.0...v3.4.1

v3.4.0

20 Sep 00:07
Compare
Choose a tag to compare

New Feature: Use mockReturnValue after or without calledWith usage

Works with mockReturnValue, mockResolvedValue, mockRejectedValue, mockImplementation

Previously you were not allowed to set a default implementation with a mock return function without a calledWith:

when(fn).mockReturnValue(true)
fn() // ⚠️ Threw error!

Now you can!

when(fn).mockReturnValue(true)
fn() // ✅ Returns `true`

Also you could set the default after using calledWith:

when(fn).mockReturnValue('b') // ✅ Before was supported
when(fn).calledWith(1).mockReturnValue('a')
when(fn).mockReturnValue('b') // ⚠️ After was not

Now you can!

when(fn).mockReturnValue('b') // ✅ Before is still supported
when(fn).calledWith(1).mockReturnValue('a')
when(fn).mockReturnValue('b') // ✅ After is now supported

New Feature: Keep original function implementation when not matched

class TheClass {
  fn () {
    return 'real'
  }
}
const instance = new TheClass()

const spy = jest.spyOn(instance, 'fn')
when(spy)
  .calledWith(1)
  .mockReturnValue('mock')
expect(instance.fn(2)).toBe('real') // ✅ Successfully falls back to the real underlying spied function

Installation Improvement: Jest is now a peer dependency

This helps avoid installing lots of duplicated dependencies.

We presume that you were not implicitly relying on jest-when to install the jest dependency in your codebase. And assuming this we are able to bump by a minor instead of major version.

Tested with jest 24, 25, 26 and 27, it should work with other versions as well. We will likely not work to support more than the last couple versions without community PRs.

Commits:

  • Allow defaults to be set up after calledWiths or even without any calledWiths cc6ca58
  • Merge pull request #82 from mfressdorf/master fe6dba9
  • Merge pull request #79 from timkindberg/dependabot/npm_and_yarn/path-parse-1.0.7 bbb5396
  • Merge pull request #74 from timkindberg/dependabot/npm_and_yarn/hosted-git-info-2.8.9 6a523f9
  • Merge pull request #84 from chyzwar/mmake-jest-peer-deps 26a907a
  • Merge branch 'master' into mmake-jest-peer-deps 1379b18
  • Merge pull request #85 from AndrewSouthpaw/replace-bunyan-with-smaller-logger d591350
  • Fully remove logger. fb72636
  • Replace bunyan with more stable, zero-dependency logger. 5624306
  • fix(): lower peer deps to jest 24, make it non breaking ad2084a
  • feat(): make jest an peer dependancy 4092a59
  • Keep original function implementation when not matched 9b81e2b
  • Bump path-parse from 1.0.5 to 1.0.7 1eca1bc
  • Bump hosted-git-info from 2.6.0 to 2.8.9 40e8ef1
  • Merge pull request #73 from timkindberg/dependabot/npm_and_yarn/glob-parent-5.1.2 8af0e4c
  • Merge pull request #72 from timkindberg/dependabot/npm_and_yarn/normalize-url-4.5.1 f8c822e
  • Merge pull request #71 from timkindberg/dependabot/npm_and_yarn/trim-newlines-3.0.1 5c3ef60
  • Merge pull request #70 from timkindberg/dependabot/npm_and_yarn/lodash-4.17.21 0a35f5b
  • Merge pull request #69 from timkindberg/dependabot/npm_and_yarn/handlebars-4.7.7 83e8d88
  • Bump glob-parent from 5.1.1 to 5.1.2 73da46c
  • Bump normalize-url from 4.5.0 to 4.5.1 2577978
  • Bump trim-newlines from 3.0.0 to 3.0.1 a3b5623
  • Bump lodash from 4.17.19 to 4.17.21 d397bc1
  • Bump handlebars from 4.5.3 to 4.7.7 a32694c

v3.3.1...v3.4.0

Fix calling `calledWith` with `null`

03 May 14:12
Compare
Choose a tag to compare

This should work again now:

const fn = jest.fn();
when(fn).calledWith(null).mockReturnValue('yay!');
expect(fn(null)).toBe('yay!');

v3.3.0

30 Apr 18:37
Compare
Choose a tag to compare

Match All Args with when.allArgs

This release adds support for matching or asserting against all of the arguments together using when.allArgs:

Just pass a single special matcher, when.allArgs. Pass when.allArgs an array predicate function.

calledWith(when.allArgs(anArrayPredicateFn))

The array predicate function will receive all of the arguments as an array as well as the powerful equals utility from Jasmine. It should return true if the args are a match, or false if not.

calledWith(
  when.allArgs(
    (allArgs: Array<any>, equals: Function) => boolean
  )
)

This allows some convenient patterns:

  • Less verbose for variable args where all need to be of a certain type or match (e.g. all numbers)
  • Can be useful for partial matching, because you can assert just the first arg for example and ignore the rest

Examples

All args should be numbers:

const areNumbers = (args, equals) => args.every(arg => equals(arg, expect.any(Number)))
when(fn).calledWith(when.allArgs(areNumbers)).mockReturnValue('yay!')

expect(fn(3, 6, 9)).toEqual('yay!')
expect(fn(3, 666)).toEqual('yay!')
expect(fn(-100, 2, 3.234234, 234, 90e3)).toEqual('yay!')
expect(fn(123, 'not a number')).toBeUndefined()

Single arg match:

const argAtIndex = (index, matcher) => when.allArgs((args, equals) => equals(args[index], matcher))

when(fn).calledWith(argAtIndex(0, expect.any(Number))).mockReturnValue('yay!')

expect(fn(3, 6, 9)).toEqual('yay!')
expect(fn(3, 666)).toEqual('yay!')
expect(fn(-100, 2, 3.234234, 234, 90e3)).toEqual('yay!')
expect(fn(123, 'not a number')).toBeUndefined()

Partial match, only first defined matching args matter:

const fn = jest.fn()
const partialArgs = (...argsToMatch) => when.allArgs((args, equals) => equals(args, expect.arrayContaining(argsToMatch)))

when(fn)
  .calledWith(partialArgs(1, 2, 3))
  .mockReturnValue('x')

expect(fn(1, 2, 3)).toEqual('x')
expect(fn(1, 2, 3, 4, 5, 6)).toEqual('x')
expect(fn(1, 2)).toBeUndefined()
expect(fn(1, 2, 4)).toBeUndefined()

v3.2.0

07 Feb 01:27
Compare
Choose a tag to compare

New Feature: Function Matchers

You can now pass a predicate function as a matcher. The function will receive the arg and will be considered a match if the function returns true.

It works with both calledWith and expectCalledWith.

Just wrap any regular function (cannot be a jest mock or spy!) with when.

const mock = jest.fn()
const myFunctionMatcher = when(arg => arg === 'hello' ? true : false)

when(mock).calledWith(myFunctionMatcher).mockReturnValue('x')

expect(mock('hello')).toEqual('x')
expect(mock('bye')).toEqual(undefined)

More Examples

const fn = jest.fn()

// Some predicate functions to be used as arg matchers
const allValuesTrue = when((arg) => Object.values(arg).every(Boolean))
const numberDivisibleBy3 = when((arg) => arg % 3 === 0)

when(fn)
    // Pass the predicate functions here as matchers
    .calledWith(allValuesTrue, numberDivisibleBy3)
    .mockReturnValue('x')

expect(fn({ foo: true, bar: true }, 9)).toEqual('x')
expect(fn({ foo: true, bar: false }, 9)).toEqual(undefined)
expect(fn({ foo: true, bar: true }, 13)).toEqual(undefined)

This should add some really great flexibility and allow for some great custom matcher utils.

For example, you can more easily use jest-when now for mocking your fetch calls for both back or front end testing.

// fetch will come in as a jest mock fn
import fetch from '~/our/companies/fetch.js'
// userService uses the fetch.js file underneath the hood to perform an outward api call
import userService from '~/someService.js'
import _ from 'lodash'

// Here's where we mocked it
jest.mock('~/our/companies/fetch.js', () => jest.fn())

// Build up a collection of custom matcher functions!!
const bodyHas = match => when(obj => _.isMatch(JSON.parse(obj).body, match))

test('calls an api', () => {
    when(fetch)
        .calledWith(
            expect.stringContaining("api/user/44"), 
            bodyHas({ firstName: "Tim" )
        )
        .mockReturnValue(Response(...))
    const user = await userService.updateUser(44, { firstName: "Tim" })
    expect()...
})

Commits

  • v3.2.0 fb0dbe6
  • Add np for releases 414c2d2
  • Docs for function matchers 36cd03f
  • Merge remote-tracking branch 'origin/master' ba0bcd3
  • Support function matchers 6190463
  • Merge pull request #60 from jlissner/patch-1 ea8f335
  • Update README.md f5df9a3
  • Add ThoughtWorks Adopt Badge b415550
  • Merge pull request #56 from timkindberg/dependabot/npm_and_yarn/ini-1.3.7 435aa82
  • Bump ini from 1.3.5 to 1.3.7 b739bb8

v3.1.0...v3.2.0

v3.0.1

01 Nov 17:10
Compare
Choose a tag to compare
  • Merge pull request #55 from tjenkinson/fix-matching-exact-args d4f496f
  • elaborate tests a bit more 88f5e2b
  • fix matching exact args fa68b7f

v3.0.0...v3.0.1

v3.0.0

29 Oct 01:22
Compare
Choose a tag to compare

Breaking Changes

WhenMocks will no longer pass if you don't explicitly provide every argument to calledWith.

const fn = jest.fn()
when(fn).calledWith(1, 2).mockReturnValue(true)
const returnValue = fn(1, 2, 3, 4)
expect(returnValue).toBe(true)  // Passed in v2, Fails in v3

In v3 pass matchers for every argument:

when(fn).calledWith(1, 2, 3, 4).mockReturnValue(true)

Or, remember you can use expect.anything() if you want flexibility:

when(fn).calledWith(1, 2, expect.anything(), expect.anything()).mockReturnValue(true)

Or, make your own handy util:

const restAnything = num => Array(num).fill(expect.anything())

when(fn).calledWith(1, 2, ...restAnything(2)).mockReturnValue(true)

Full Explanation

Contributor @tjenkinson found an inconsistency with jest-when's behavior compared to jest. I've always stated that the goal of this lib is to feel like a missing feature of jest and I try to stay as consistent as possible with the precedences set by jest's APIs.

In regular jest if you write this test it will fail.

const spy = jest.fn();
spy(1, 2, 3);
expect(spy).toHaveBeenCalledWith(1, 2);

Output:

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: 1, 2
    Received: 1, 2, 3

This is not balanced with v2 of jest-when which allows you to provide a subset of the args, essentially the unspecified args implicitly behaved like an expect.anything() matcher. So I'd either have to provide a special syntax for folks who wanted to opt out of the implicit anything behavior, or merge this fix. So since I consider this a miss in our original API we did the latter.

Commits

  • do not match when too many args provided 26abfc6

v2.8.1...v3.0.0