From 8e85f3df741a3fc7bbedfc25bc68a64a3d727f54 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Feb 2022 21:10:31 +0800 Subject: [PATCH 01/16] Create toHaveSpyInteractions matcher This matcher checks all the properties of a given spy object and checks whether at least one of the spies has been called. It returns true if one or more of the spies of the spy object has been called and false otherwise. --- src/core/matchers/requireMatchers.js | 1 + src/core/matchers/toHaveSpyInteractions.js | 73 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) mode change 100644 => 100755 src/core/matchers/requireMatchers.js create mode 100755 src/core/matchers/toHaveSpyInteractions.js diff --git a/src/core/matchers/requireMatchers.js b/src/core/matchers/requireMatchers.js old mode 100644 new mode 100755 index c14ceac9..3c9d6983 --- a/src/core/matchers/requireMatchers.js +++ b/src/core/matchers/requireMatchers.js @@ -27,6 +27,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveClass', + 'toHaveSpyInteractions', 'toMatch', 'toThrow', 'toThrowError', diff --git a/src/core/matchers/toHaveSpyInteractions.js b/src/core/matchers/toHaveSpyInteractions.js new file mode 100755 index 00000000..d9c3c72e --- /dev/null +++ b/src/core/matchers/toHaveSpyInteractions.js @@ -0,0 +1,73 @@ +getJasmineRequireObj().toHaveSpyInteractions = function(j$) { + var getErrorMsg = j$.formatErrorMsg( + '', + 'expect().toHaveSpyInteractions()' + ); + + /** + * {@link expect} the actual (a {@link SpyObj}) spies to have been called. + * @function + * @name matchers#toHaveSpyInteractions + * @since 4.0.0 + * @example + * expect(mySpyObj).toHaveSpyInteractions(); + * expect(mySpyObj).not.toHaveSpyInteractions(); + */ + function toHaveSpyInteractions(matchersUtil) { + return { + compare: function(actual) { + var result = {}; + + if (!j$.isObject_(actual)) { + throw new Error( + getErrorMsg( + 'Expected a spy object, but got ' + matchersUtil.pp(actual) + '.' + ) + ); + } + + if (arguments.length > 1) { + throw new Error(getErrorMsg('Does not take arguments')); + } + + result.pass = false; + let hasSpy = false; + const calledSpies = []; + for (const spy of Object.values(actual)) { + if (!j$.isSpy(spy)) continue; + hasSpy = true; + + if (spy.calls.any()) { + result.pass = true; + calledSpies.push([spy.and.identity, spy.calls.count()]); + } + } + + if (!hasSpy) { + throw new Error( + getErrorMsg( + 'Expected a spy object with spies, but object has no spies.' + ) + ); + } + + let resultMessage = 'Expected spy object spies to have been called'; + if (result.pass) { + resultMessage += ', the following spies were called: '; + resultMessage += calledSpies + .map(([spyName, spyCount]) => { + return `${spyName} called ${spyCount} time(s)`; + }) + .join(', '); + } else { + resultMessage += ', but no spies were called.'; + } + result.message = resultMessage; + + return result; + } + }; + } + + return toHaveSpyInteractions; +}; From 7b01003d0b31997c3e76ee196467184386130921 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Feb 2022 21:11:42 +0800 Subject: [PATCH 02/16] Add specs for the new toHaveSpyInteractions matcher --- spec/core/integration/MatchersSpec.js | 22 ++++++++ spec/core/matchers/toHaveSpyInteractions.js | 57 +++++++++++++++++++++ 2 files changed, 79 insertions(+) mode change 100644 => 100755 spec/core/integration/MatchersSpec.js create mode 100755 spec/core/matchers/toHaveSpyInteractions.js diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js old mode 100644 new mode 100755 index 9e3f7f6c..30a9e656 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -625,6 +625,28 @@ describe('Matchers (Integration)', function() { }); }); + describe('toHaveSpyInteractions', function() { + let spyObj; + beforeEach(function() { + spyObj = env.createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.otherMethod = function() {}; + }); + + verifyPasses(function(env) { + spyObj.spyA(); + env.expect(spyObj).toHaveSpyInteractions(); + }); + + verifyFails(function(env) { + env.expect(spyObj).toHaveSpyInteractions(); + }); + + verifyFails(function(env) { + spyObj.otherMethod(); + env.expect(spyObj).toHaveSpyInteractions(); + }); + }); + describe('toMatch', function() { verifyPasses(function(env) { env.expect('foo').toMatch(/oo$/); diff --git a/spec/core/matchers/toHaveSpyInteractions.js b/spec/core/matchers/toHaveSpyInteractions.js new file mode 100755 index 00000000..98f8c270 --- /dev/null +++ b/spec/core/matchers/toHaveSpyInteractions.js @@ -0,0 +1,57 @@ +describe('toHaveSpyInteractions', () => { + let spyObj; + beforeEach(() => { + spyObj = jasmineUnderTest.createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.otherMethod = function() {}; + }); + + it('detects spy interactions', () => { + spyObj.spyA(); + expect(spyObj).toHaveSpyInteractions(); + }); + + it('detects multiple spy interactions', () => { + spyObj.spyA(); + spyObj.spyB(); + spyObj.spyA(); + expect(spyObj).toHaveSpyInteractions(); + }); + + it('detects no spy interactions', () => { + expect(spyObj).not.toHaveSpyInteractions(); + }); + + it('ignores non-observed spy object interactions', () => { + spyObj.otherMethod(); + expect(spyObj).not.toHaveSpyInteractions(); + }); + + [true, 123, 'string'].forEach(testValue => { + it(`throws error if a non-object (${testValue}) is passed`, () => { + expect(() => { + expect(true).toHaveSpyInteractions(); + }).toThrowError(Error, /Expected a spy object, but got/); + }); + }); + + [['argument'], [false, 0]].forEach(testValue => { + it(`throws error if arguments (${testValue}) are passed`, () => { + expect(() => { + expect(spyObj).toHaveSpyInteractions(...testValue); + }).toThrowError(Error, /Does not take arguments/); + }); + }); + + it('throws error if spy object has no spies', () => { + const newSpyObj = jasmine.createSpyObj('OtherClass', ['method']); + // Removing spy since spy objects cannot be created without spies. + newSpyObj.method = function() {}; + + expect(() => { + expect(newSpyObj).toHaveSpyInteractions(); + }).toThrowError( + Error, + /Expected a spy object with spies, but object has no spies/ + ); + }); +}); From d7d75abc4214dad2f79ff95e7aa9c9e8ff99e444 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 20:14:06 +0800 Subject: [PATCH 03/16] Rename spec to include Spec to allow running --- .../{toHaveSpyInteractions.js => toHaveSpyInteractionsSpec.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/core/matchers/{toHaveSpyInteractions.js => toHaveSpyInteractionsSpec.js} (100%) diff --git a/spec/core/matchers/toHaveSpyInteractions.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js similarity index 100% rename from spec/core/matchers/toHaveSpyInteractions.js rename to spec/core/matchers/toHaveSpyInteractionsSpec.js From b2c2e086410561bd3fcac23c9171616a8899e6dd Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 20:15:09 +0800 Subject: [PATCH 04/16] Remove @since tag from JSDoc --- src/core/matchers/toHaveSpyInteractions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/matchers/toHaveSpyInteractions.js b/src/core/matchers/toHaveSpyInteractions.js index d9c3c72e..37bf5481 100755 --- a/src/core/matchers/toHaveSpyInteractions.js +++ b/src/core/matchers/toHaveSpyInteractions.js @@ -8,7 +8,6 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) { * {@link expect} the actual (a {@link SpyObj}) spies to have been called. * @function * @name matchers#toHaveSpyInteractions - * @since 4.0.0 * @example * expect(mySpyObj).toHaveSpyInteractions(); * expect(mySpyObj).not.toHaveSpyInteractions(); From 2e8b4774890864aa8328471ec46b62580901238e Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:14:43 +0800 Subject: [PATCH 05/16] Change arrow functions with anonymous functions --- .../matchers/toHaveSpyInteractionsSpec.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 98f8c270..4092719a 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -1,53 +1,53 @@ -describe('toHaveSpyInteractions', () => { +describe('toHaveSpyInteractions', function() { let spyObj; - beforeEach(() => { - spyObj = jasmineUnderTest.createSpyObj('NewClass', ['spyA', 'spyB']); + beforeEach(function() { + spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.otherMethod = function() {}; }); - it('detects spy interactions', () => { + it('detects spy interactions', function() { spyObj.spyA(); expect(spyObj).toHaveSpyInteractions(); }); - it('detects multiple spy interactions', () => { + it('detects multiple spy interactions', function() { spyObj.spyA(); spyObj.spyB(); spyObj.spyA(); expect(spyObj).toHaveSpyInteractions(); }); - it('detects no spy interactions', () => { + it('detects no spy interactions', function() { expect(spyObj).not.toHaveSpyInteractions(); }); - it('ignores non-observed spy object interactions', () => { + it('ignores non-observed spy object interactions', function() { spyObj.otherMethod(); expect(spyObj).not.toHaveSpyInteractions(); }); - [true, 123, 'string'].forEach(testValue => { - it(`throws error if a non-object (${testValue}) is passed`, () => { - expect(() => { + [true, 123, 'string'].forEach(function(testValue) { + it(`throws error if a non-object (${testValue}) is passed`, function() { + expect(function() { expect(true).toHaveSpyInteractions(); }).toThrowError(Error, /Expected a spy object, but got/); }); }); - [['argument'], [false, 0]].forEach(testValue => { - it(`throws error if arguments (${testValue}) are passed`, () => { - expect(() => { + [['argument'], [false, 0]].forEach(function(testValue) { + it(`throws error if arguments (${testValue}) are passed`, function() { + expect(function() { expect(spyObj).toHaveSpyInteractions(...testValue); }).toThrowError(Error, /Does not take arguments/); }); }); - it('throws error if spy object has no spies', () => { + it('throws error if spy object has no spies', function() { const newSpyObj = jasmine.createSpyObj('OtherClass', ['method']); // Removing spy since spy objects cannot be created without spies. newSpyObj.method = function() {}; - expect(() => { + expect(function() { expect(newSpyObj).toHaveSpyInteractions(); }).toThrowError( Error, From c13dd26c4b1379f92a12a4bce020f6053506bec3 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:18:25 +0800 Subject: [PATCH 06/16] Change set up to each of the formats This goes against DRY principle, but it was recommended by Jasmine team to reduce coupling between tests. --- .../matchers/toHaveSpyInteractionsSpec.js | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 4092719a..34ff66fd 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -1,33 +1,40 @@ describe('toHaveSpyInteractions', function() { - let spyObj; - beforeEach(function() { - spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); - spyObj.otherMethod = function() {}; - }); - it('detects spy interactions', function() { + let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.spyA(); + expect(spyObj).toHaveSpyInteractions(); }); it('detects multiple spy interactions', function() { + let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.spyA(); spyObj.spyB(); spyObj.spyA(); + expect(spyObj).toHaveSpyInteractions(); }); it('detects no spy interactions', function() { + let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + expect(spyObj).not.toHaveSpyInteractions(); }); it('ignores non-observed spy object interactions', function() { + let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.otherMethod(); + expect(spyObj).not.toHaveSpyInteractions(); }); [true, 123, 'string'].forEach(function(testValue) { it(`throws error if a non-object (${testValue}) is passed`, function() { + let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + expect(function() { expect(true).toHaveSpyInteractions(); }).toThrowError(Error, /Expected a spy object, but got/); @@ -36,6 +43,8 @@ describe('toHaveSpyInteractions', function() { [['argument'], [false, 0]].forEach(function(testValue) { it(`throws error if arguments (${testValue}) are passed`, function() { + let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + expect(function() { expect(spyObj).toHaveSpyInteractions(...testValue); }).toThrowError(Error, /Does not take arguments/); @@ -43,12 +52,12 @@ describe('toHaveSpyInteractions', function() { }); it('throws error if spy object has no spies', function() { - const newSpyObj = jasmine.createSpyObj('OtherClass', ['method']); + const spyObj = jasmine.createSpyObj('NewClass', ['method']); // Removing spy since spy objects cannot be created without spies. - newSpyObj.method = function() {}; + spyObj.method = function() {}; expect(function() { - expect(newSpyObj).toHaveSpyInteractions(); + expect(spyObj).toHaveSpyInteractions(); }).toThrowError( Error, /Expected a spy object with spies, but object has no spies/ From 2a5673e6ab1e07a79d28d4a707f9498292ee8195 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:20:21 +0800 Subject: [PATCH 07/16] Change jasmine to jasmineUnderTest --- spec/core/matchers/toHaveSpyInteractionsSpec.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 34ff66fd..4b8982ed 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -1,6 +1,6 @@ describe('toHaveSpyInteractions', function() { it('detects spy interactions', function() { - let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.spyA(); @@ -8,7 +8,7 @@ describe('toHaveSpyInteractions', function() { }); it('detects multiple spy interactions', function() { - let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.spyA(); spyObj.spyB(); @@ -18,13 +18,13 @@ describe('toHaveSpyInteractions', function() { }); it('detects no spy interactions', function() { - let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); expect(spyObj).not.toHaveSpyInteractions(); }); it('ignores non-observed spy object interactions', function() { - let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.otherMethod(); @@ -33,7 +33,7 @@ describe('toHaveSpyInteractions', function() { [true, 123, 'string'].forEach(function(testValue) { it(`throws error if a non-object (${testValue}) is passed`, function() { - let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); expect(function() { expect(true).toHaveSpyInteractions(); @@ -43,7 +43,7 @@ describe('toHaveSpyInteractions', function() { [['argument'], [false, 0]].forEach(function(testValue) { it(`throws error if arguments (${testValue}) are passed`, function() { - let spyObj = jasmine.createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); expect(function() { expect(spyObj).toHaveSpyInteractions(...testValue); @@ -52,7 +52,7 @@ describe('toHaveSpyInteractions', function() { }); it('throws error if spy object has no spies', function() { - const spyObj = jasmine.createSpyObj('NewClass', ['method']); + const spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['method']); // Removing spy since spy objects cannot be created without spies. spyObj.method = function() {}; From a7eff79db0ca62f37e04f57d8e1a7cc5b6040f0e Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:26:59 +0800 Subject: [PATCH 08/16] Simplify test for arguments passed --- spec/core/matchers/toHaveSpyInteractionsSpec.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 4b8982ed..2e1bfaff 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -41,14 +41,12 @@ describe('toHaveSpyInteractions', function() { }); }); - [['argument'], [false, 0]].forEach(function(testValue) { - it(`throws error if arguments (${testValue}) are passed`, function() { - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); + it('throws error if arguments are passed', function() { + let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); - expect(function() { - expect(spyObj).toHaveSpyInteractions(...testValue); - }).toThrowError(Error, /Does not take arguments/); - }); + expect(function() { + expect(spyObj).toHaveSpyInteractions('an argument'); + }).toThrowError(Error, /Does not take arguments/); }); it('throws error if spy object has no spies', function() { From aba0c98eb95181460eb838b33833f32c54c6022e Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:31:40 +0800 Subject: [PATCH 09/16] Fix unit test to include testValue instead of a constant value --- spec/core/matchers/toHaveSpyInteractionsSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 2e1bfaff..1fdfb562 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -36,7 +36,7 @@ describe('toHaveSpyInteractions', function() { let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); expect(function() { - expect(true).toHaveSpyInteractions(); + expect(testValue).toHaveSpyInteractions(); }).toThrowError(Error, /Expected a spy object, but got/); }); }); From 091cd8c3b6bb9f4088e3eae89360bc56cd3389b4 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:34:28 +0800 Subject: [PATCH 10/16] Remove spyObj setup from test that does not require it --- spec/core/matchers/toHaveSpyInteractionsSpec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 1fdfb562..c4b7b788 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -33,8 +33,6 @@ describe('toHaveSpyInteractions', function() { [true, 123, 'string'].forEach(function(testValue) { it(`throws error if a non-object (${testValue}) is passed`, function() { - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); - expect(function() { expect(testValue).toHaveSpyInteractions(); }).toThrowError(Error, /Expected a spy object, but got/); From 00fd4a819fe01b1db7bd449289c92bcbbf6fdc24 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 21:48:03 +0800 Subject: [PATCH 11/16] Refactor tests to depend on jasmineUnderTest --- .../matchers/toHaveSpyInteractionsSpec.js | 68 +++++++++++++------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index c4b7b788..3720adb8 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -1,59 +1,85 @@ -describe('toHaveSpyInteractions', function() { - it('detects spy interactions', function() { +describe('toHaveSpyInteractions', function () { + + it('detects spy interactions', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.spyA(); - expect(spyObj).toHaveSpyInteractions(); + let result = matcher.compare(spyObj); + expect(result.pass).toBe(true); + expect(result.message).toContain( + 'Expected spy object spies to have been called' + ); }); - it('detects multiple spy interactions', function() { + it('detects multiple spy interactions', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.spyA(); spyObj.spyB(); spyObj.spyA(); - expect(spyObj).toHaveSpyInteractions(); + let result = matcher.compare(spyObj); + expect(result.pass).toBe(true); + expect(result.message).toContain( + 'Expected spy object spies to have been called' + ); }); - it('detects no spy interactions', function() { + it('detects no spy interactions', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); - expect(spyObj).not.toHaveSpyInteractions(); + let result = matcher.compare(spyObj); + expect(result.pass).toBe(false); + expect(result.message).toContain( + 'Expected spy object spies to have been called' + ); }); - it('ignores non-observed spy object interactions', function() { + it('ignores non-observed spy object interactions', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.otherMethod = function () { }; spyObj.otherMethod(); - expect(spyObj).not.toHaveSpyInteractions(); + let result = matcher.compare(spyObj); + expect(result.pass).toBe(false); + expect(result.message).toContain( + 'Expected spy object spies to have been called' + ); }); - [true, 123, 'string'].forEach(function(testValue) { - it(`throws error if a non-object (${testValue}) is passed`, function() { - expect(function() { - expect(testValue).toHaveSpyInteractions(); + [true, 123, 'string'].forEach(function (testValue) { + it(`throws error if a non-object (${testValue}) is passed`, function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); + + expect(function () { + matcher.compare(testValue); }).toThrowError(Error, /Expected a spy object, but got/); }); }); - it('throws error if arguments are passed', function() { + it('throws error if arguments are passed', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); - expect(function() { - expect(spyObj).toHaveSpyInteractions('an argument'); + expect(function () { + matcher.compare(spyObj, 'an argument'); }).toThrowError(Error, /Does not take arguments/); }); - it('throws error if spy object has no spies', function() { - const spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['method']); + it('throws error if spy object has no spies', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); + const spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['notSpy']); // Removing spy since spy objects cannot be created without spies. - spyObj.method = function() {}; + spyObj.notSpy = function () { }; - expect(function() { - expect(spyObj).toHaveSpyInteractions(); + expect(function () { + matcher.compare(spyObj); }).toThrowError( Error, /Expected a spy object with spies, but object has no spies/ From faf210ab4cd6015016ba74a2c96f83b3550937ba Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 23:00:27 +0800 Subject: [PATCH 12/16] Remove dependency on matchersUtil for test --- src/core/matchers/toHaveSpyInteractions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/matchers/toHaveSpyInteractions.js b/src/core/matchers/toHaveSpyInteractions.js index 37bf5481..a6a5fdc9 100755 --- a/src/core/matchers/toHaveSpyInteractions.js +++ b/src/core/matchers/toHaveSpyInteractions.js @@ -20,7 +20,7 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) { if (!j$.isObject_(actual)) { throw new Error( getErrorMsg( - 'Expected a spy object, but got ' + matchersUtil.pp(actual) + '.' + 'Expected a spy object, but got ' + typeof actual + '.' ) ); } From 1660015c12022cfcb5757c68cbe8d7296239cdde Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Wed, 16 Mar 2022 23:01:20 +0800 Subject: [PATCH 13/16] Run formatter --- .../matchers/toHaveSpyInteractionsSpec.js | 53 +++++++++++-------- src/core/matchers/toHaveSpyInteractions.js | 4 +- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 3720adb8..80d4b700 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -1,8 +1,9 @@ -describe('toHaveSpyInteractions', function () { - - it('detects spy interactions', function () { +describe('toHaveSpyInteractions', function() { + it('detects spy interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.spyA(); @@ -13,9 +14,11 @@ describe('toHaveSpyInteractions', function () { ); }); - it('detects multiple spy interactions', function () { + it('detects multiple spy interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['spyA', 'spyB']); spyObj.spyA(); spyObj.spyB(); @@ -28,9 +31,11 @@ describe('toHaveSpyInteractions', function () { ); }); - it('detects no spy interactions', function () { + it('detects no spy interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['spyA', 'spyB']); let result = matcher.compare(spyObj); expect(result.pass).toBe(false); @@ -39,10 +44,12 @@ describe('toHaveSpyInteractions', function () { ); }); - it('ignores non-observed spy object interactions', function () { + it('ignores non-observed spy object interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); - spyObj.otherMethod = function () { }; + let spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['spyA', 'spyB']); + spyObj.otherMethod = function() {}; spyObj.otherMethod(); @@ -53,32 +60,36 @@ describe('toHaveSpyInteractions', function () { ); }); - [true, 123, 'string'].forEach(function (testValue) { - it(`throws error if a non-object (${testValue}) is passed`, function () { + [true, 123, 'string'].forEach(function(testValue) { + it(`throws error if a non-object (${testValue}) is passed`, function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - expect(function () { + expect(function() { matcher.compare(testValue); }).toThrowError(Error, /Expected a spy object, but got/); }); }); - it('throws error if arguments are passed', function () { + it('throws error if arguments are passed', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - let spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['spyA', 'spyB']); + let spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['spyA', 'spyB']); - expect(function () { + expect(function() { matcher.compare(spyObj, 'an argument'); }).toThrowError(Error, /Does not take arguments/); }); - it('throws error if spy object has no spies', function () { + it('throws error if spy object has no spies', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - const spyObj = jasmineUnderTest.getEnv().createSpyObj('NewClass', ['notSpy']); + const spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['notSpy']); // Removing spy since spy objects cannot be created without spies. - spyObj.notSpy = function () { }; + spyObj.notSpy = function() {}; - expect(function () { + expect(function() { matcher.compare(spyObj); }).toThrowError( Error, diff --git a/src/core/matchers/toHaveSpyInteractions.js b/src/core/matchers/toHaveSpyInteractions.js index a6a5fdc9..58f6d44d 100755 --- a/src/core/matchers/toHaveSpyInteractions.js +++ b/src/core/matchers/toHaveSpyInteractions.js @@ -19,9 +19,7 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) { if (!j$.isObject_(actual)) { throw new Error( - getErrorMsg( - 'Expected a spy object, but got ' + typeof actual + '.' - ) + getErrorMsg('Expected a spy object, but got ' + typeof actual + '.') ); } From a8a6577cd7aea2fea96a1ac28a4857526517b60f Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Thu, 17 Mar 2022 20:16:22 +0800 Subject: [PATCH 14/16] Replace parameterized test with different expectations This approach makes it hard to scale and goes against DRY and debuggability vs the previous approach which followed Python parameterized testing, but this was the recommendation of the Jasmine team to keep it consistent with other tests. Further tests here could be adding other types like Array, Map, WeakMap, Set, WeakSet... --- .../matchers/toHaveSpyInteractionsSpec.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 80d4b700..2a7bb436 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -60,14 +60,20 @@ describe('toHaveSpyInteractions', function() { ); }); - [true, 123, 'string'].forEach(function(testValue) { - it(`throws error if a non-object (${testValue}) is passed`, function() { - let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); + it(`throws error if a non-object is passed`, function() { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); - expect(function() { - matcher.compare(testValue); - }).toThrowError(Error, /Expected a spy object, but got/); - }); + expect(function() { + matcher.compare(true); + }).toThrowError(Error, /Expected a spy object, but got/); + + expect(function () { + matcher.compare(123); + }).toThrowError(Error, /Expected a spy object, but got/); + + expect(function () { + matcher.compare('string'); + }).toThrowError(Error, /Expected a spy object, but got/); }); it('throws error if arguments are passed', function() { From e470fb56d74817e9e164ae3ba552d4faa9e2efa6 Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Thu, 17 Mar 2022 21:06:51 +0800 Subject: [PATCH 15/16] Refactor error message to account for negate comparisons The message return on negate clause was not expected. This makes it negative to match expectation. This also add tests for the change, and renames some tests to make it more clear. --- .../matchers/toHaveSpyInteractionsSpec.js | 35 ++++++++++++------- src/core/matchers/toHaveSpyInteractions.js | 20 ++++++----- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 2a7bb436..68ca3e91 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -1,5 +1,5 @@ describe('toHaveSpyInteractions', function() { - it('detects spy interactions', function() { + it('passes when there are spy interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest .getEnv() @@ -9,12 +9,9 @@ describe('toHaveSpyInteractions', function() { let result = matcher.compare(spyObj); expect(result.pass).toBe(true); - expect(result.message).toContain( - 'Expected spy object spies to have been called' - ); }); - it('detects multiple spy interactions', function() { + it('passes when there are multiple spy interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest .getEnv() @@ -26,12 +23,9 @@ describe('toHaveSpyInteractions', function() { let result = matcher.compare(spyObj); expect(result.pass).toBe(true); - expect(result.message).toContain( - 'Expected spy object spies to have been called' - ); }); - it('detects no spy interactions', function() { + it('fails when there are no spy interactions', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest .getEnv() @@ -44,7 +38,22 @@ describe('toHaveSpyInteractions', function() { ); }); - it('ignores non-observed spy object interactions', function() { + it('shows the right message is negated', function () { + let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); + let spyObj = jasmineUnderTest + .getEnv() + .createSpyObj('NewClass', ['spyA', 'spyB']); + + spyObj.spyA(); + + let result = matcher.compare(spyObj); + expect(result.pass).toBe(true); + expect(result.message).toContain( // Will be shown only on negate. + 'Expected spy object spies not to have been called' + ); + }); + + it('fails when only non-observed spy object interactions are interacted', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest .getEnv() @@ -60,7 +69,7 @@ describe('toHaveSpyInteractions', function() { ); }); - it(`throws error if a non-object is passed`, function() { + it(`throws an error if a non-object is passed`, function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); expect(function() { @@ -76,7 +85,7 @@ describe('toHaveSpyInteractions', function() { }).toThrowError(Error, /Expected a spy object, but got/); }); - it('throws error if arguments are passed', function() { + it('throws an error if arguments are passed', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest .getEnv() @@ -87,7 +96,7 @@ describe('toHaveSpyInteractions', function() { }).toThrowError(Error, /Does not take arguments/); }); - it('throws error if spy object has no spies', function() { + it('throws an error if the spy object has no spies', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); const spyObj = jasmineUnderTest .getEnv() diff --git a/src/core/matchers/toHaveSpyInteractions.js b/src/core/matchers/toHaveSpyInteractions.js index 58f6d44d..c2c83eb2 100755 --- a/src/core/matchers/toHaveSpyInteractions.js +++ b/src/core/matchers/toHaveSpyInteractions.js @@ -1,4 +1,4 @@ -getJasmineRequireObj().toHaveSpyInteractions = function(j$) { +getJasmineRequireObj().toHaveSpyInteractions = function (j$) { var getErrorMsg = j$.formatErrorMsg( '', 'expect().toHaveSpyInteractions()' @@ -14,7 +14,7 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) { */ function toHaveSpyInteractions(matchersUtil) { return { - compare: function(actual) { + compare: function (actual) { var result = {}; if (!j$.isObject_(actual)) { @@ -48,16 +48,18 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) { ); } - let resultMessage = 'Expected spy object spies to have been called'; + let resultMessage; if (result.pass) { - resultMessage += ', the following spies were called: '; - resultMessage += calledSpies - .map(([spyName, spyCount]) => { + resultMessage = + 'Expected spy object spies not to have been called, ' + + 'but the following spies were called: '; + resultMessage += calledSpies.map(([spyName, spyCount]) => { return `${spyName} called ${spyCount} time(s)`; - }) - .join(', '); + }).join(', '); } else { - resultMessage += ', but no spies were called.'; + resultMessage = + 'Expected spy object spies to have been called, ' + + 'but no spies were called.'; } result.message = resultMessage; From c5db9398862e95cde258aa9246be3e27401fd44a Mon Sep 17 00:00:00 2001 From: Nito Buendia Date: Thu, 17 Mar 2022 21:09:14 +0800 Subject: [PATCH 16/16] Run cleanup --- spec/core/matchers/toHaveSpyInteractionsSpec.js | 9 +++++---- src/core/matchers/toHaveSpyInteractions.js | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/core/matchers/toHaveSpyInteractionsSpec.js b/spec/core/matchers/toHaveSpyInteractionsSpec.js index 68ca3e91..3d0e577c 100755 --- a/spec/core/matchers/toHaveSpyInteractionsSpec.js +++ b/spec/core/matchers/toHaveSpyInteractionsSpec.js @@ -38,7 +38,7 @@ describe('toHaveSpyInteractions', function() { ); }); - it('shows the right message is negated', function () { + it('shows the right message is negated', function() { let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions(); let spyObj = jasmineUnderTest .getEnv() @@ -48,7 +48,8 @@ describe('toHaveSpyInteractions', function() { let result = matcher.compare(spyObj); expect(result.pass).toBe(true); - expect(result.message).toContain( // Will be shown only on negate. + expect(result.message).toContain( + // Will be shown only on negate. 'Expected spy object spies not to have been called' ); }); @@ -76,11 +77,11 @@ describe('toHaveSpyInteractions', function() { matcher.compare(true); }).toThrowError(Error, /Expected a spy object, but got/); - expect(function () { + expect(function() { matcher.compare(123); }).toThrowError(Error, /Expected a spy object, but got/); - expect(function () { + expect(function() { matcher.compare('string'); }).toThrowError(Error, /Expected a spy object, but got/); }); diff --git a/src/core/matchers/toHaveSpyInteractions.js b/src/core/matchers/toHaveSpyInteractions.js index c2c83eb2..ca091ead 100755 --- a/src/core/matchers/toHaveSpyInteractions.js +++ b/src/core/matchers/toHaveSpyInteractions.js @@ -1,4 +1,4 @@ -getJasmineRequireObj().toHaveSpyInteractions = function (j$) { +getJasmineRequireObj().toHaveSpyInteractions = function(j$) { var getErrorMsg = j$.formatErrorMsg( '', 'expect().toHaveSpyInteractions()' @@ -14,7 +14,7 @@ getJasmineRequireObj().toHaveSpyInteractions = function (j$) { */ function toHaveSpyInteractions(matchersUtil) { return { - compare: function (actual) { + compare: function(actual) { var result = {}; if (!j$.isObject_(actual)) { @@ -53,9 +53,11 @@ getJasmineRequireObj().toHaveSpyInteractions = function (j$) { resultMessage = 'Expected spy object spies not to have been called, ' + 'but the following spies were called: '; - resultMessage += calledSpies.map(([spyName, spyCount]) => { + resultMessage += calledSpies + .map(([spyName, spyCount]) => { return `${spyName} called ${spyCount} time(s)`; - }).join(', '); + }) + .join(', '); } else { resultMessage = 'Expected spy object spies to have been called, ' +