diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 21510660..1f079a35 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -147,6 +147,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', + 'toHaveBeenCalledOnceWith', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveClass', @@ -5802,6 +5803,76 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { return toHaveBeenCalledBefore; }; +getJasmineRequireObj().toHaveBeenCalledOnceWith = function (j$) { + + var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledOnceWith(...arguments)'); + + /** + * {@link expect} the actual (a {@link Spy}) to have been called exactly once, and exactly with the particular arguments. + * @function + * @name matchers#toHaveBeenCalledOnceWith + * @since 3.6.0 + * @param {...Object} - The arguments to look for + * @example + * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2); + */ + function toHaveBeenCalledOnceWith(util, customEqualityTesters) { + return { + compare: function () { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1); + + if (!j$.isSpy(actual)) { + throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + } + + var prettyPrintedCalls = actual.calls.allArgs().map(function (argsForCall) { + return ' ' + j$.pp(argsForCall); + }); + + if (actual.calls.count() === 1 && util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + return { + pass: true, + message: 'Expected spy ' + actual.and.identity + ' to have been called 0 times, multiple times, or once, but with arguments different from:\n' + + ' ' + j$.pp(expectedArgs) + '\n' + + 'But the actual call was:\n' + + prettyPrintedCalls.join(',\n') + '.\n\n' + }; + } + + function getDiffs() { + return actual.calls.allArgs().map(function (argsForCall, callIx) { + var diffBuilder = new j$.DiffBuilder(); + util.equals(argsForCall, expectedArgs, customEqualityTesters, diffBuilder); + return diffBuilder.getMessage(); + }); + } + + function butString() { + switch (actual.calls.count()) { + case 0: + return 'But it was never called.\n\n'; + case 1: + return 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n' + getDiffs().join('\n') + '\n\n'; + default: + return 'But the actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n'; + } + } + + return { + pass: false, + message: 'Expected spy ' + actual.and.identity + ' to have been called only once, and with given args:\n' + + ' ' + j$.pp(expectedArgs) + '\n' + + butString() + }; + } + }; + } + + return toHaveBeenCalledOnceWith; +}; + getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledTimes()'); diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js index c1d2dd09..5e253e08 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -573,6 +573,22 @@ describe('Matchers (Integration)', function() { }); }); + describe('toHaveBeenCalledOnceWith', function() { + verifyPasses(function(env) { + var spy = env.createSpy(); + spy('5', 3); + env.addCustomEqualityTester(function(a, b) { + return a.toString() === b.toString(); + }); + env.expect(spy).toHaveBeenCalledOnceWith(5, 3); + }); + + verifyFails(function(env) { + var spy = env.createSpy(); + env.expect(spy).toHaveBeenCalledOnceWith(5, 3); + }); + }); + describe('toHaveClass', function() { beforeEach(function() { this.domHelpers = jasmine.getEnv().domHelpers(); diff --git a/spec/core/matchers/toHaveBeenCalledOnceWithSpec.js b/spec/core/matchers/toHaveBeenCalledOnceWithSpec.js new file mode 100644 index 00000000..d5bdab43 --- /dev/null +++ b/spec/core/matchers/toHaveBeenCalledOnceWithSpec.js @@ -0,0 +1,89 @@ +describe("toHaveBeenCalledOnceWith", function () { + + it("passes when the actual was called only once and with matching parameters", function () { + var util = jasmineUnderTest.matchersUtil, + matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util), + calledSpy = new jasmineUnderTest.Spy('called-spy'), + result; + + calledSpy('a', 'b'); + result = matcher.compare(calledSpy, 'a', 'b'); + + expect(result.pass).toBe(true); + expect(result.message).toEqual("Expected spy called-spy to have been called 0 times, multiple times, or once, but with arguments different from:\n [ 'a', 'b' ]\nBut the actual call was:\n [ 'a', 'b' ].\n\n"); + }); + + it("passes through the custom equality testers", function () { + var util = jasmineUnderTest.matchersUtil; + spyOn(util, 'contains').and.returnValue(false); + + var customEqualityTesters = [function () { return true; }], + matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util, customEqualityTesters), + calledSpy = new jasmineUnderTest.Spy('called-spy'); + + calledSpy('a', 'b'); + matcher.compare(calledSpy, 'a', 'b'); + + expect(util.contains).toHaveBeenCalledWith([['a', 'b']], ['a', 'b'], customEqualityTesters); + }); + + it("fails when the actual was never called", function () { + var util = jasmineUnderTest.matchersUtil, + matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util), + calledSpy = new jasmineUnderTest.Spy('called-spy'), + result; + + result = matcher.compare(calledSpy, 'a', 'b'); + + expect(result.pass).toBe(false); + expect(result.message).toEqual("Expected spy called-spy to have been called only once, and with given args:\n [ 'a', 'b' ]\nBut it was never called.\n\n"); + }); + + it("fails when the actual was called once with different parameters", function () { + var util = jasmineUnderTest.matchersUtil, + matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util), + calledSpy = new jasmineUnderTest.Spy('called-spy'), + result; + + calledSpy('a', 'c'); + result = matcher.compare(calledSpy, 'a', 'b'); + + expect(result.pass).toBe(false); + expect(result.message).toEqual("Expected spy called-spy to have been called only once, and with given args:\n [ 'a', 'b' ]\nBut the actual call was:\n [ 'a', 'c' ].\nExpected $[1] = 'c' to equal 'b'.\n\n"); + }); + + it("fails when the actual was called multiple times with expected parameters", function () { + var util = jasmineUnderTest.matchersUtil, + matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util), + calledSpy = new jasmineUnderTest.Spy('called-spy'), + result; + + calledSpy('a', 'b'); + calledSpy('a', 'b'); + result = matcher.compare(calledSpy, 'a', 'b'); + + expect(result.pass).toBe(false); + expect(result.message).toEqual("Expected spy called-spy to have been called only once, and with given args:\n [ 'a', 'b' ]\nBut the actual calls were:\n [ 'a', 'b' ],\n [ 'a', 'b' ].\n\n"); + }); + + it("fails when the actual was called multiple times (one of them - with expected parameters)", function () { + var util = jasmineUnderTest.matchersUtil, + matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util), + calledSpy = new jasmineUnderTest.Spy('called-spy'), + result; + + calledSpy('a', 'b'); + calledSpy('a', 'c'); + result = matcher.compare(calledSpy, 'a', 'b'); + + expect(result.pass).toBe(false); + expect(result.message).toEqual("Expected spy called-spy to have been called only once, and with given args:\n [ 'a', 'b' ]\nBut the actual calls were:\n [ 'a', 'b' ],\n [ 'a', 'c' ].\n\n"); + }); + + it("throws an exception when the actual is not a spy", function () { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(), + fn = function () { }; + + expect(function () { matcher.compare(fn) }).toThrowError(/Expected a spy, but got Function./); + }); +}); diff --git a/src/core/matchers/requireMatchers.js b/src/core/matchers/requireMatchers.js index e0b256e5..c14ceac9 100644 --- a/src/core/matchers/requireMatchers.js +++ b/src/core/matchers/requireMatchers.js @@ -23,6 +23,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', + 'toHaveBeenCalledOnceWith', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveClass', diff --git a/src/core/matchers/toHaveBeenCalledOnceWith.js b/src/core/matchers/toHaveBeenCalledOnceWith.js new file mode 100644 index 00000000..035848b0 --- /dev/null +++ b/src/core/matchers/toHaveBeenCalledOnceWith.js @@ -0,0 +1,69 @@ +getJasmineRequireObj().toHaveBeenCalledOnceWith = function (j$) { + + var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledOnceWith(...arguments)'); + + /** + * {@link expect} the actual (a {@link Spy}) to have been called exactly once, and exactly with the particular arguments. + * @function + * @name matchers#toHaveBeenCalledOnceWith + * @since 3.6.0 + * @param {...Object} - The arguments to look for + * @example + * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2); + */ + function toHaveBeenCalledOnceWith(util, customEqualityTesters) { + return { + compare: function () { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1); + + if (!j$.isSpy(actual)) { + throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + } + + var prettyPrintedCalls = actual.calls.allArgs().map(function (argsForCall) { + return ' ' + j$.pp(argsForCall); + }); + + if (actual.calls.count() === 1 && util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + return { + pass: true, + message: 'Expected spy ' + actual.and.identity + ' to have been called 0 times, multiple times, or once, but with arguments different from:\n' + + ' ' + j$.pp(expectedArgs) + '\n' + + 'But the actual call was:\n' + + prettyPrintedCalls.join(',\n') + '.\n\n' + }; + } + + function getDiffs() { + return actual.calls.allArgs().map(function (argsForCall, callIx) { + var diffBuilder = new j$.DiffBuilder(); + util.equals(argsForCall, expectedArgs, customEqualityTesters, diffBuilder); + return diffBuilder.getMessage(); + }); + } + + function butString() { + switch (actual.calls.count()) { + case 0: + return 'But it was never called.\n\n'; + case 1: + return 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n' + getDiffs().join('\n') + '\n\n'; + default: + return 'But the actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n'; + } + } + + return { + pass: false, + message: 'Expected spy ' + actual.and.identity + ' to have been called only once, and with given args:\n' + + ' ' + j$.pp(expectedArgs) + '\n' + + butString() + }; + } + }; + } + + return toHaveBeenCalledOnceWith; +};