/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import { test, expect } from './fixtures'; import { expect as expectUnderTest, mock } from '../../packages/playwright/bundles/expect/src/expectBundleImpl'; import Immutable from 'immutable'; const expectUnderTestAsAny = expectUnderTest as any; expectUnderTest.extend({ optionalFn(fn?: unknown) { const pass = fn === undefined || typeof fn === 'function'; return { message: () => 'expect either a function or undefined', pass }; }, }); // Given a Jest mock function, return a minimal mock of a spy. const createSpy = (fn: mock.Mock) => { const spy = function() { }; spy.calls = { all() { return fn.mock.calls.map(args => ({ args })); }, count() { return fn.mock.calls.length; }, }; return spy; }; for (const called of ['toBeCalled', 'toHaveBeenCalled']) { test.describe(called, () => { test('works only on spies or mock.fn', () => { const fn = function fn() { }; expect(() => expectUnderTest(fn)[called]()).toThrowErrorMatchingSnapshot(); }); test('passes when called', () => { const fn = mock.fn(); fn('arg0', 'arg1', 'arg2'); expectUnderTest(createSpy(fn))[called](); expectUnderTest(fn)[called](); expect(() => expectUnderTest(fn).not[called]()).toThrowErrorMatchingSnapshot(); }); test('.not passes when called', () => { const fn = mock.fn(); const spy = createSpy(fn); expectUnderTest(spy).not[called](); expectUnderTest(fn).not[called](); expect(() => expectUnderTest(spy)[called]()).toThrowErrorMatchingSnapshot(); }); test('fails with any argument passed', () => { const fn = mock.fn(); fn(); expect(() => expectUnderTest(fn)[called](555)).toThrowErrorMatchingSnapshot(); }); test('.not fails with any argument passed', () => { const fn = mock.fn(); expect(() => expectUnderTest(fn).not[called](555), ).toThrowErrorMatchingSnapshot(); }); test('includes the custom mock name in the error message', () => { const fn = mock.fn().mockName('named-mock'); fn(); expectUnderTest(fn)[called](); expect(() => expectUnderTest(fn).not[called]()).toThrowErrorMatchingSnapshot(); }); }); } for (const calledTimes of ['toBeCalledTimes', 'toHaveBeenCalledTimes']) { test.describe(calledTimes, () => { test('.not works only on spies or mock.fn', () => { const fn = function fn() { }; expect(() => expectUnderTest(fn).not[calledTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('only accepts a number argument', () => { const fn = mock.fn(); fn(); expectUnderTest(fn)[calledTimes](1); [{}, [], true, 'a', new Map(), () => { }].forEach(value => { expect(() => expectUnderTest(fn)[calledTimes](value), ).toThrowErrorMatchingSnapshot(); }); }); test('.not only accepts a number argument', () => { const fn = mock.fn(); expectUnderTest(fn).not[calledTimes](1); [{}, [], true, 'a', new Map(), () => { }].forEach(value => { expect(() => expectUnderTest(fn).not[calledTimes](value), ).toThrowErrorMatchingSnapshot(); }); }); test('passes if function called equal to expected times', () => { const fn = mock.fn(); fn(); fn(); const spy = createSpy(fn); expectUnderTest(spy)[calledTimes](2); expectUnderTest(fn)[calledTimes](2); expect(() => expectUnderTest(spy).not[calledTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('.not passes if function called more than expected times', () => { const fn = mock.fn(); fn(); fn(); fn(); const spy = createSpy(fn); expectUnderTest(spy)[calledTimes](3); expectUnderTest(spy).not[calledTimes](2); expectUnderTest(fn)[calledTimes](3); expectUnderTest(fn).not[calledTimes](2); expect(() => expectUnderTest(fn)[calledTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('.not passes if function called less than expected times', () => { const fn = mock.fn(); fn(); const spy = createSpy(fn); expectUnderTest(spy)[calledTimes](1); expectUnderTest(spy).not[calledTimes](2); expectUnderTest(fn)[calledTimes](1); expectUnderTest(fn).not[calledTimes](2); expect(() => expectUnderTest(fn)[calledTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('includes the custom mock name in the error message', () => { const fn = mock.fn().mockName('named-mock'); fn(); expect(() => expectUnderTest(fn)[calledTimes](2), ).toThrowErrorMatchingSnapshot(); }); }); } for (const calledWith of [ 'lastCalledWith', 'toHaveBeenLastCalledWith', 'nthCalledWith', 'toHaveBeenNthCalledWith', 'toBeCalledWith', 'toHaveBeenCalledWith', ]) { test.describe(calledWith, () => { function isToHaveNth( calledWith: string, ): calledWith is 'nthCalledWith' | 'toHaveBeenNthCalledWith' { return ( calledWith === 'nthCalledWith' || calledWith === 'toHaveBeenNthCalledWith' ); } test('works only on spies or mock.fn', () => { const fn = function fn() { }; if (isToHaveNth(calledWith)) { expect(() => expectUnderTest(fn)[calledWith](3), ).toThrowErrorMatchingSnapshot(); } else { expect(() => expectUnderTest(fn)[calledWith]()).toThrowErrorMatchingSnapshot(); } }); test('works when not called', () => { const fn = mock.fn(); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn)).not[calledWith](1, 'foo', 'bar'); expectUnderTest(fn).not[calledWith](1, 'foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith](1, 'foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn)).not[calledWith]('foo', 'bar'); expectUnderTest(fn).not[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } }); test('works with no arguments', () => { const fn = mock.fn(); fn(); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn))[calledWith](1); expectUnderTest(fn)[calledWith](1); } else { expectUnderTest(createSpy(fn))[calledWith](); expectUnderTest(fn)[calledWith](); } }); test("works with arguments that don't match", () => { const fn = mock.fn(); fn('foo', 'bar1'); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn)).not[calledWith](1, 'foo', 'bar'); expectUnderTest(fn).not[calledWith](1, 'foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith](1, 'foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn)).not[calledWith]('foo', 'bar'); expectUnderTest(fn).not[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } }); test("works with arguments that don't match in number of arguments", () => { const fn = mock.fn(); fn('foo', 'bar', 'plop'); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn)).not[calledWith](1, 'foo', 'bar'); expectUnderTest(fn).not[calledWith](1, 'foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith](1, 'foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn)).not[calledWith]('foo', 'bar'); expectUnderTest(fn).not[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } }); test("works with arguments that don't match with matchers", () => { const fn = mock.fn(); fn('foo', 'bar'); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn)).not[calledWith]( 1, expectUnderTest.any(String), expectUnderTest.any(Number), ); expectUnderTest(fn).not[calledWith]( 1, expectUnderTest.any(String), expectUnderTest.any(Number), ); expect(() => expectUnderTest(fn)[calledWith]( 1, expectUnderTest.any(String), expectUnderTest.any(Number), ), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn)).not[calledWith]( expectUnderTest.any(String), expectUnderTest.any(Number), ); expectUnderTest(fn).not[calledWith]( expectUnderTest.any(String), expectUnderTest.any(Number), ); expect(() => expectUnderTest(fn)[calledWith]( expectUnderTest.any(String), expectUnderTest.any(Number), ), ).toThrowErrorMatchingSnapshot(); } }); test("works with arguments that don't match with matchers even when argument is undefined", () => { const fn = mock.fn(); fn('foo', undefined); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn)).not[calledWith]( 1, 'foo', expectUnderTest.any(String), ); expectUnderTest(fn).not[calledWith](1, 'foo', expectUnderTest.any(String)); expect(() => expectUnderTest(fn)[calledWith](1, 'foo', expectUnderTest.any(String)), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn)).not[calledWith]('foo', expectUnderTest.any(String)); expectUnderTest(fn).not[calledWith]('foo', expectUnderTest.any(String)); expect(() => expectUnderTest(fn)[calledWith]('foo', expectUnderTest.any(String)), ).toThrowErrorMatchingSnapshot(); } }); test("works with arguments that don't match in size even if one is an optional matcher", () => { // issue 12463 const fn = mock.fn(); fn('foo'); if (isToHaveNth(calledWith)) { expectUnderTest(fn).not[calledWith](1, 'foo', expectUnderTestAsAny.optionalFn()); expect(() => expectUnderTest(fn)[calledWith](1, 'foo', expectUnderTestAsAny.optionalFn()), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn).not[calledWith]('foo', expectUnderTestAsAny.optionalFn()); expect(() => expectUnderTest(fn)[calledWith]('foo', expectUnderTestAsAny.optionalFn()), ).toThrowErrorMatchingSnapshot(); } }); test('works with arguments that match', () => { const fn = mock.fn(); fn('foo', 'bar'); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn))[calledWith](1, 'foo', 'bar'); expectUnderTest(fn)[calledWith](1, 'foo', 'bar'); expect(() => expectUnderTest(fn).not[calledWith](1, 'foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn))[calledWith]('foo', 'bar'); expectUnderTest(fn)[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn).not[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } }); test('works with arguments that match with matchers', () => { const fn = mock.fn(); fn('foo', 'bar'); if (isToHaveNth(calledWith)) { expectUnderTest(createSpy(fn))[calledWith]( 1, expectUnderTest.any(String), expectUnderTest.any(String), ); expectUnderTest(fn)[calledWith]( 1, expectUnderTest.any(String), expectUnderTest.any(String), ); expect(() => expectUnderTest(fn).not[calledWith]( 1, expectUnderTest.any(String), expectUnderTest.any(String), ), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(createSpy(fn))[calledWith]( expectUnderTest.any(String), expectUnderTest.any(String), ); expectUnderTest(fn)[calledWith]( expectUnderTest.any(String), expectUnderTest.any(String), ); expect(() => expectUnderTest(fn).not[calledWith]( expectUnderTest.any(String), expectUnderTest.any(String), ), ).toThrowErrorMatchingSnapshot(); } }); test('works with trailing undefined arguments', () => { const fn = mock.fn(); fn('foo', undefined); if (isToHaveNth(calledWith)) { expect(() => expectUnderTest(fn)[calledWith](1, 'foo'), ).toThrowErrorMatchingSnapshot(); } else { expect(() => expectUnderTest(fn)[calledWith]('foo'), ).toThrowErrorMatchingSnapshot(); } }); test('works with trailing undefined arguments if requested by the match query', () => { const fn = mock.fn(); fn('foo', undefined); if (isToHaveNth(calledWith)) { expectUnderTest(fn)[calledWith](1, 'foo', undefined); expect(() => expectUnderTest(fn).not[calledWith](1, 'foo', undefined), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[calledWith]('foo', undefined); expect(() => expectUnderTest(fn).not[calledWith]('foo', undefined), ).toThrowErrorMatchingSnapshot(); } }); test('works with trailing undefined arguments when explicitly requested as optional by matcher', () => { // issue 12463 const fn = mock.fn(); fn('foo', undefined); if (isToHaveNth(calledWith)) { expectUnderTest(fn)[calledWith](1, 'foo', expectUnderTestAsAny.optionalFn()); expect(() => expectUnderTest(fn).not[calledWith](1, 'foo', expectUnderTestAsAny.optionalFn()), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[calledWith]('foo', expectUnderTestAsAny.optionalFn()); expect(() => expectUnderTest(fn).not[calledWith]('foo', expectUnderTestAsAny.optionalFn()), ).toThrowErrorMatchingSnapshot(); } }); test('works with Map', () => { const fn = mock.fn(); const m1 = new Map([ [1, 2], [2, 1], ]); const m2 = new Map([ [1, 2], [2, 1], ]); const m3 = new Map([ ['a', 'b'], ['b', 'a'], ]); fn(m1); if (isToHaveNth(calledWith)) { expectUnderTest(fn)[calledWith](1, m2); expectUnderTest(fn).not[calledWith](1, m3); expect(() => expectUnderTest(fn).not[calledWith](1, m2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[calledWith](1, m3), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[calledWith](m2); expectUnderTest(fn).not[calledWith](m3); expect(() => expectUnderTest(fn).not[calledWith](m2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[calledWith](m3), ).toThrowErrorMatchingSnapshot(); } }); test('works with Set', () => { const fn = mock.fn(); const s1 = new Set([1, 2]); const s2 = new Set([1, 2]); const s3 = new Set([3, 4]); fn(s1); if (isToHaveNth(calledWith)) { expectUnderTest(fn)[calledWith](1, s2); expectUnderTest(fn).not[calledWith](1, s3); expect(() => expectUnderTest(fn).not[calledWith](1, s2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[calledWith](1, s3), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[calledWith](s2); expectUnderTest(fn).not[calledWith](s3); expect(() => expectUnderTest(fn).not[calledWith](s2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[calledWith](s3), ).toThrowErrorMatchingSnapshot(); } }); test('works with Immutable.js objects', () => { const fn = mock.fn(); const directlyCreated = Immutable.Map([['a', { b: 'c' }]]); const indirectlyCreated = Immutable.Map().set('a', { b: 'c' }); fn(directlyCreated, indirectlyCreated); if (isToHaveNth(calledWith)) { expectUnderTest(fn)[calledWith](1, indirectlyCreated, directlyCreated); expect(() => expectUnderTest(fn).not[calledWith](1, indirectlyCreated, directlyCreated), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[calledWith](indirectlyCreated, directlyCreated); expect(() => expectUnderTest(fn).not[calledWith](indirectlyCreated, directlyCreated), ).toThrowErrorMatchingSnapshot(); } }); if (!isToHaveNth(calledWith)) { test('works with many arguments', () => { const fn = mock.fn(); fn('foo1', 'bar'); fn('foo', 'bar1'); fn('foo', 'bar'); expectUnderTest(fn)[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn).not[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); }); test("works with many arguments that don't match", () => { const fn = mock.fn(); fn('foo', 'bar1'); fn('foo', 'bar2'); fn('foo', 'bar3'); expectUnderTest(fn).not[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn)[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); }); } if (isToHaveNth(calledWith)) { test('works with three calls', () => { const fn = mock.fn(); fn('foo1', 'bar'); fn('foo', 'bar1'); fn('foo', 'bar'); expectUnderTest(fn)[calledWith](1, 'foo1', 'bar'); expectUnderTest(fn)[calledWith](2, 'foo', 'bar1'); expectUnderTest(fn)[calledWith](3, 'foo', 'bar'); expect(() => { expectUnderTest(fn).not[calledWith](1, 'foo1', 'bar'); }).toThrowErrorMatchingSnapshot(); }); test('positive throw matcher error for n that is not positive integer', async () => { const fn = mock.fn(); fn('foo1', 'bar'); expect(() => { expectUnderTest(fn)[calledWith](0, 'foo1', 'bar'); }).toThrowErrorMatchingSnapshot(); }); test('positive throw matcher error for n that is not integer', async () => { const fn = mock.fn(); fn('foo1', 'bar'); expect(() => { expectUnderTest(fn)[calledWith](0.1, 'foo1', 'bar'); }).toThrowErrorMatchingSnapshot(); }); test('negative throw matcher error for n that is not integer', async () => { const fn = mock.fn(); fn('foo1', 'bar'); expect(() => { expectUnderTest(fn).not[calledWith](Infinity, 'foo1', 'bar'); }).toThrowErrorMatchingSnapshot(); }); } test('includes the custom mock name in the error message', () => { const fn = mock.fn().mockName('named-mock'); fn('foo', 'bar'); if (isToHaveNth(calledWith)) { expectUnderTest(fn)[calledWith](1, 'foo', 'bar'); expect(() => expectUnderTest(fn).not[calledWith](1, 'foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[calledWith]('foo', 'bar'); expect(() => expectUnderTest(fn).not[calledWith]('foo', 'bar'), ).toThrowErrorMatchingSnapshot(); } }); }); } for (const returned of ['toReturn', 'toHaveReturned']) { test.describe(returned, () => { test('.not works only on mock.fn', () => { const fn = function fn() { }; expect(() => expectUnderTest(fn).not[returned]()).toThrowErrorMatchingSnapshot(); }); test('throw matcher error if received is spy', () => { const spy = createSpy(mock.fn()); expect(() => expectUnderTest(spy)[returned]()).toThrowErrorMatchingSnapshot(); }); test('passes when returned', () => { const fn = mock.fn(() => 42); fn(); expectUnderTest(fn)[returned](); expect(() => expectUnderTest(fn).not[returned]()).toThrowErrorMatchingSnapshot(); }); test('passes when undefined is returned', () => { const fn = mock.fn(() => undefined); fn(); expectUnderTest(fn)[returned](); expect(() => expectUnderTest(fn).not[returned]()).toThrowErrorMatchingSnapshot(); }); test('passes when at least one call does not throw', () => { const fn = mock.fn((causeError: boolean) => { if (causeError) throw new Error('Error!'); return 42; }); fn(false); try { fn(true); } catch { // ignore error } fn(false); expectUnderTest(fn)[returned](); expect(() => expectUnderTest(fn).not[returned]()).toThrowErrorMatchingSnapshot(); }); test('.not passes when not returned', () => { const fn = mock.fn(); expectUnderTest(fn).not[returned](); expect(() => expectUnderTest(fn)[returned]()).toThrowErrorMatchingSnapshot(); }); test('.not passes when all calls throw', () => { const fn = mock.fn(() => { throw new Error('Error!'); }); try { fn(); } catch { // ignore error } try { fn(); } catch { // ignore error } expectUnderTest(fn).not[returned](); expect(() => expectUnderTest(fn)[returned]()).toThrowErrorMatchingSnapshot(); }); test('.not passes when a call throws undefined', () => { const fn = mock.fn(() => { throw undefined; }); try { fn(); } catch { // ignore error } expectUnderTest(fn).not[returned](); expect(() => expectUnderTest(fn)[returned]()).toThrowErrorMatchingSnapshot(); }); test('fails with any argument passed', () => { const fn = mock.fn(); fn(); expect(() => expectUnderTest(fn)[returned](555)).toThrowErrorMatchingSnapshot(); }); test('.not fails with any argument passed', () => { const fn = mock.fn(); expect(() => expectUnderTest(fn).not[returned](555), ).toThrowErrorMatchingSnapshot(); }); test('includes the custom mock name in the error message', () => { const fn = mock.fn(() => 42).mockName('named-mock'); fn(); expectUnderTest(fn)[returned](); expect(() => expectUnderTest(fn).not[returned]()).toThrowErrorMatchingSnapshot(); }); test('incomplete recursive calls are handled properly', () => { // sums up all integers from 0 -> value, using recursion const fn: mock.Mock<(value: number) => number> = mock.fn(value => { if (value === 0) { // Before returning from the base case of recursion, none of the // calls have returned yet. expectUnderTest(fn).not[returned](); expect(() => expectUnderTest(fn)[returned]()).toThrowErrorMatchingSnapshot(); return 0; } else { return value + fn(value - 1); } }); fn(3); }); }); } for (const returnedTimes of ['toReturnTimes', 'toHaveReturnedTimes']) { test.describe(returnedTimes, () => { test('throw matcher error if received is spy', () => { const spy = createSpy(mock.fn()); expect(() => expectUnderTest(spy).not[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('only accepts a number argument', () => { const fn = mock.fn(() => 42); fn(); expectUnderTest(fn)[returnedTimes](1); [{}, [], true, 'a', new Map(), () => { }].forEach(value => { expect(() => expectUnderTest(fn)[returnedTimes](value), ).toThrowErrorMatchingSnapshot(); }); }); test('.not only accepts a number argument', () => { const fn = mock.fn(() => 42); expectUnderTest(fn).not[returnedTimes](2); [{}, [], true, 'a', new Map(), () => { }].forEach(value => { expect(() => expectUnderTest(fn).not[returnedTimes](value), ).toThrowErrorMatchingSnapshot(); }); }); test('passes if function returned equal to expected times', () => { const fn = mock.fn(() => 42); fn(); fn(); expectUnderTest(fn)[returnedTimes](2); expect(() => expectUnderTest(fn).not[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('calls that return undefined are counted as returns', () => { const fn = mock.fn(() => undefined); fn(); fn(); expectUnderTest(fn)[returnedTimes](2); expect(() => expectUnderTest(fn).not[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('.not passes if function returned more than expected times', () => { const fn = mock.fn(() => 42); fn(); fn(); fn(); expectUnderTest(fn)[returnedTimes](3); expectUnderTest(fn).not[returnedTimes](2); expect(() => expectUnderTest(fn)[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('.not passes if function called less than expected times', () => { const fn = mock.fn(() => 42); fn(); expectUnderTest(fn)[returnedTimes](1); expectUnderTest(fn).not[returnedTimes](2); expect(() => expectUnderTest(fn)[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('calls that throw are not counted', () => { const fn = mock.fn((causeError: boolean) => { if (causeError) throw new Error('Error!'); return 42; }); fn(false); try { fn(true); } catch { // ignore error } fn(false); expectUnderTest(fn).not[returnedTimes](3); expect(() => expectUnderTest(fn)[returnedTimes](3), ).toThrowErrorMatchingSnapshot(); }); test('calls that throw undefined are not counted', () => { const fn = mock.fn((causeError: boolean) => { if (causeError) throw undefined; return 42; }); fn(false); try { fn(true); } catch { // ignore error } fn(false); expectUnderTest(fn)[returnedTimes](2); expect(() => expectUnderTest(fn).not[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); test('includes the custom mock name in the error message', () => { const fn = mock.fn(() => 42).mockName('named-mock'); fn(); fn(); expectUnderTest(fn)[returnedTimes](2); expect(() => expectUnderTest(fn)[returnedTimes](1), ).toThrowErrorMatchingSnapshot(); }); test('incomplete recursive calls are handled properly', () => { // sums up all integers from 0 -> value, using recursion const fn: mock.Mock<(value: number) => number> = mock.fn(value => { if (value === 0) { return 0; } else { const recursiveResult = fn(value - 1); if (value === 2) { // Only 2 of the recursive calls have returned at this point expectUnderTest(fn)[returnedTimes](2); expect(() => expectUnderTest(fn).not[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); } return value + recursiveResult; } }); fn(3); }); }); } for (const returnedWith of [ 'lastReturnedWith', 'toHaveLastReturnedWith', 'nthReturnedWith', 'toHaveNthReturnedWith', 'toReturnWith', 'toHaveReturnedWith', ]) { test.describe(returnedWith, () => { function isToHaveNth( returnedWith: string, ): returnedWith is 'nthReturnedWith' | 'toHaveNthReturnedWith' { return ( returnedWith === 'nthReturnedWith' || returnedWith === 'toHaveNthReturnedWith' ); } function isToHaveLast( returnedWith: string, ): returnedWith is 'lastReturnedWith' | 'toHaveLastReturnedWith' { return ( returnedWith === 'lastReturnedWith' || returnedWith === 'toHaveLastReturnedWith' ); } test('works only on spies or mock.fn', () => { const fn = function fn() { }; expect(() => expectUnderTest(fn)[returnedWith]()).toThrowErrorMatchingSnapshot(); }); test('works when not called', () => { const fn = mock.fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn).not[returnedWith](1, 'foo'); expect(() => expectUnderTest(fn)[returnedWith](1, 'foo'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn).not[returnedWith]('foo'); expect(() => expectUnderTest(fn)[returnedWith]('foo'), ).toThrowErrorMatchingSnapshot(); } }); test('works with no arguments', () => { const fn = mock.fn(); fn(); if (isToHaveNth(returnedWith)) expectUnderTest(fn)[returnedWith](1); else expectUnderTest(fn)[returnedWith](); }); test('works with argument that does not match', () => { const fn = mock.fn(() => 'foo'); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn).not[returnedWith](1, 'bar'); expect(() => expectUnderTest(fn)[returnedWith](1, 'bar'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn).not[returnedWith]('bar'); expect(() => expectUnderTest(fn)[returnedWith]('bar'), ).toThrowErrorMatchingSnapshot(); } }); test('works with argument that does match', () => { const fn = mock.fn(() => 'foo'); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn)[returnedWith](1, 'foo'); expect(() => expectUnderTest(fn).not[returnedWith](1, 'foo'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[returnedWith]('foo'); expect(() => expectUnderTest(fn).not[returnedWith]('foo'), ).toThrowErrorMatchingSnapshot(); } }); test('works with undefined', () => { const fn = mock.fn(() => undefined); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn)[returnedWith](1, undefined); expect(() => expectUnderTest(fn).not[returnedWith](1, undefined), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[returnedWith](undefined); expect(() => expectUnderTest(fn).not[returnedWith](undefined), ).toThrowErrorMatchingSnapshot(); } }); test('works with Map', () => { const m1 = new Map([ [1, 2], [2, 1], ]); const m2 = new Map([ [1, 2], [2, 1], ]); const m3 = new Map([ ['a', 'b'], ['b', 'a'], ]); const fn = mock.fn(() => m1); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn)[returnedWith](1, m2); expectUnderTest(fn).not[returnedWith](1, m3); expect(() => expectUnderTest(fn).not[returnedWith](1, m2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[returnedWith](1, m3), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[returnedWith](m2); expectUnderTest(fn).not[returnedWith](m3); expect(() => expectUnderTest(fn).not[returnedWith](m2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[returnedWith](m3), ).toThrowErrorMatchingSnapshot(); } }); test('works with Set', () => { const s1 = new Set([1, 2]); const s2 = new Set([1, 2]); const s3 = new Set([3, 4]); const fn = mock.fn(() => s1); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn)[returnedWith](1, s2); expectUnderTest(fn).not[returnedWith](1, s3); expect(() => expectUnderTest(fn).not[returnedWith](1, s2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[returnedWith](1, s3), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[returnedWith](s2); expectUnderTest(fn).not[returnedWith](s3); expect(() => expectUnderTest(fn).not[returnedWith](s2), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[returnedWith](s3), ).toThrowErrorMatchingSnapshot(); } }); test('works with Immutable.js objects directly created', () => { const directlyCreated = Immutable.Map([['a', { b: 'c' }]]); const fn = mock.fn(() => directlyCreated); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn)[returnedWith](1, directlyCreated); expect(() => expectUnderTest(fn).not[returnedWith](1, directlyCreated), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[returnedWith](directlyCreated); expect(() => expectUnderTest(fn).not[returnedWith](directlyCreated), ).toThrowErrorMatchingSnapshot(); } }); test('works with Immutable.js objects indirectly created', () => { const indirectlyCreated = Immutable.Map().set('a', { b: 'c' }); const fn = mock.fn(() => indirectlyCreated); fn(); if (isToHaveNth(returnedWith)) { expectUnderTest(fn)[returnedWith](1, indirectlyCreated); expect(() => expectUnderTest(fn).not[returnedWith](1, indirectlyCreated), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn)[returnedWith](indirectlyCreated); expect(() => expectUnderTest(fn).not[returnedWith](indirectlyCreated), ).toThrowErrorMatchingSnapshot(); } }); test('a call that throws is not considered to have returned', () => { const fn = mock.fn(() => { throw new Error('Error!'); }); try { fn(); } catch { // ignore error } if (isToHaveNth(returnedWith)) { // It doesn't matter what return value is tested if the call threw expectUnderTest(fn).not[returnedWith](1, 'foo'); expectUnderTest(fn).not[returnedWith](1, null); expectUnderTest(fn).not[returnedWith](1, undefined); expect(() => expectUnderTest(fn)[returnedWith](1, undefined), ).toThrowErrorMatchingSnapshot(); } else { // It doesn't matter what return value is tested if the call threw expectUnderTest(fn).not[returnedWith]('foo'); expectUnderTest(fn).not[returnedWith](null); expectUnderTest(fn).not[returnedWith](undefined); expect(() => expectUnderTest(fn)[returnedWith](undefined), ).toThrowErrorMatchingSnapshot(); } }); test('a call that throws undefined is not considered to have returned', () => { const fn = mock.fn(() => { throw undefined; }); try { fn(); } catch { // ignore error } if (isToHaveNth(returnedWith)) { // It doesn't matter what return value is tested if the call threw expectUnderTest(fn).not[returnedWith](1, 'foo'); expectUnderTest(fn).not[returnedWith](1, null); expectUnderTest(fn).not[returnedWith](1, undefined); expect(() => expectUnderTest(fn)[returnedWith](1, undefined), ).toThrowErrorMatchingSnapshot(); } else { // It doesn't matter what return value is tested if the call threw expectUnderTest(fn).not[returnedWith]('foo'); expectUnderTest(fn).not[returnedWith](null); expectUnderTest(fn).not[returnedWith](undefined); expect(() => expectUnderTest(fn)[returnedWith](undefined), ).toThrowErrorMatchingSnapshot(); } }); if (!isToHaveNth(returnedWith)) { test.describe('returnedWith', () => { test('works with more calls than the limit', () => { const fn = mock.fn<() => string>(); fn.mockReturnValueOnce('foo1'); fn.mockReturnValueOnce('foo2'); fn.mockReturnValueOnce('foo3'); fn.mockReturnValueOnce('foo4'); fn.mockReturnValueOnce('foo5'); fn.mockReturnValueOnce('foo6'); fn(); fn(); fn(); fn(); fn(); fn(); expectUnderTest(fn).not[returnedWith]('bar'); expect(() => { expectUnderTest(fn)[returnedWith]('bar'); }).toThrowErrorMatchingSnapshot(); }); test('incomplete recursive calls are handled properly', () => { // sums up all integers from 0 -> value, using recursion const fn: mock.Mock<(value: number) => number> = mock.fn(value => { if (value === 0) { // Before returning from the base case of recursion, none of the // calls have returned yet. // This test ensures that the incomplete calls are not incorrectly // interpreted as have returned undefined expectUnderTest(fn).not[returnedWith](undefined); expect(() => expectUnderTest(fn)[returnedWith](undefined), ).toThrowErrorMatchingSnapshot(); return 0; } else { return value + fn(value - 1); } }); fn(3); }); }); } if (isToHaveNth(returnedWith)) { test.describe('nthReturnedWith', () => { test('works with three calls', () => { const fn = mock.fn<() => string>(); fn.mockReturnValueOnce('foo1'); fn.mockReturnValueOnce('foo2'); fn.mockReturnValueOnce('foo3'); fn(); fn(); fn(); expectUnderTest(fn)[returnedWith](1, 'foo1'); expectUnderTest(fn)[returnedWith](2, 'foo2'); expectUnderTest(fn)[returnedWith](3, 'foo3'); expect(() => { expectUnderTest(fn).not[returnedWith](1, 'foo1'); expectUnderTest(fn).not[returnedWith](2, 'foo2'); expectUnderTest(fn).not[returnedWith](3, 'foo3'); }).toThrowErrorMatchingSnapshot(); }); test('should replace 1st, 2nd, 3rd with first, second, third', async () => { const fn = mock.fn<() => string>(); fn.mockReturnValueOnce('foo1'); fn.mockReturnValueOnce('foo2'); fn.mockReturnValueOnce('foo3'); fn(); fn(); fn(); expect(() => { expectUnderTest(fn)[returnedWith](1, 'bar1'); expectUnderTest(fn)[returnedWith](2, 'bar2'); expectUnderTest(fn)[returnedWith](3, 'bar3'); }).toThrowErrorMatchingSnapshot(); expect(() => { expectUnderTest(fn).not[returnedWith](1, 'foo1'); expectUnderTest(fn).not[returnedWith](2, 'foo2'); expectUnderTest(fn).not[returnedWith](3, 'foo3'); }).toThrowErrorMatchingSnapshot(); }); test('positive throw matcher error for n that is not positive integer', async () => { const fn = mock.fn(() => 'foo'); fn(); expect(() => { expectUnderTest(fn)[returnedWith](0, 'foo'); }).toThrowErrorMatchingSnapshot(); }); test('should reject nth value greater than number of calls', async () => { const fn = mock.fn(() => 'foo'); fn(); fn(); fn(); expect(() => { expectUnderTest(fn)[returnedWith](4, 'foo'); }).toThrowErrorMatchingSnapshot(); }); test('positive throw matcher error for n that is not integer', async () => { const fn = mock.fn<(a: string) => string>(() => 'foo'); fn('foo'); expect(() => { expectUnderTest(fn)[returnedWith](0.1, 'foo'); }).toThrowErrorMatchingSnapshot(); }); test('negative throw matcher error for n that is not number', async () => { const fn = mock.fn<(a: string) => string>(() => 'foo'); fn('foo'); expect(() => { // @ts-expect-error: Testing runtime error expectUnderTest(fn).not[returnedWith](); }).toThrowErrorMatchingSnapshot(); }); test('incomplete recursive calls are handled properly', () => { // sums up all integers from 0 -> value, using recursion const fn: mock.Mock<(value: number) => number> = mock.fn(value => { if (value === 0) { return 0; } else { const recursiveResult = fn(value - 1); if (value === 2) { // Only 2 of the recursive calls have returned at this point expectUnderTest(fn).not[returnedWith](1, 6); expectUnderTest(fn).not[returnedWith](2, 3); expectUnderTest(fn)[returnedWith](3, 1); expectUnderTest(fn)[returnedWith](4, 0); expect(() => expectUnderTest(fn)[returnedWith](1, 6), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn)[returnedWith](2, 3), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn).not[returnedWith](3, 1), ).toThrowErrorMatchingSnapshot(); expect(() => expectUnderTest(fn).not[returnedWith](4, 0), ).toThrowErrorMatchingSnapshot(); } return value + recursiveResult; } }); fn(3); }); }); } if (isToHaveLast(returnedWith)) { test.describe('lastReturnedWith', () => { test('works with three calls', () => { const fn = mock.fn<() => string>(); fn.mockReturnValueOnce('foo1'); fn.mockReturnValueOnce('foo2'); fn.mockReturnValueOnce('foo3'); fn(); fn(); fn(); expectUnderTest(fn)[returnedWith]('foo3'); expect(() => { expectUnderTest(fn).not[returnedWith]('foo3'); }).toThrowErrorMatchingSnapshot(); }); test('incomplete recursive calls are handled properly', () => { // sums up all integers from 0 -> value, using recursion const fn: mock.Mock<(value: number) => number> = mock.fn(value => { if (value === 0) { // Before returning from the base case of recursion, none of the // calls have returned yet. expectUnderTest(fn).not[returnedWith](0); expect(() => expectUnderTest(fn)[returnedWith](0), ).toThrowErrorMatchingSnapshot(); return 0; } else { return value + fn(value - 1); } }); fn(3); }); }); } test('includes the custom mock name in the error message', () => { const fn = mock.fn().mockName('named-mock'); if (isToHaveNth(returnedWith)) { expectUnderTest(fn).not[returnedWith](1, 'foo'); expect(() => expectUnderTest(fn)[returnedWith](1, 'foo'), ).toThrowErrorMatchingSnapshot(); } else { expectUnderTest(fn).not[returnedWith]('foo'); expect(() => expectUnderTest(fn)[returnedWith]('foo'), ).toThrowErrorMatchingSnapshot(); } }); }); }