Merge branch 'main' of https://github.com/nitobuendia/jasmine
* Adds toHaveSpyInteractions matcher * Merges #1959 from @nitobuenida * Fixes #1568
This commit is contained in:
@@ -152,6 +152,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
|
|||||||
'toHaveBeenCalledTimes',
|
'toHaveBeenCalledTimes',
|
||||||
'toHaveBeenCalledWith',
|
'toHaveBeenCalledWith',
|
||||||
'toHaveClass',
|
'toHaveClass',
|
||||||
|
'toHaveSpyInteractions',
|
||||||
'toMatch',
|
'toMatch',
|
||||||
'toThrow',
|
'toThrow',
|
||||||
'toThrowError',
|
'toThrowError',
|
||||||
@@ -6930,6 +6931,81 @@ getJasmineRequireObj().toHaveSize = function(j$) {
|
|||||||
return toHaveSize;
|
return toHaveSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getJasmineRequireObj().toHaveSpyInteractions = function(j$) {
|
||||||
|
var getErrorMsg = j$.formatErrorMsg(
|
||||||
|
'<toHaveSpyInteractions>',
|
||||||
|
'expect(<spyObj>).toHaveSpyInteractions()'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link expect} the actual (a {@link SpyObj}) spies to have been called.
|
||||||
|
* @function
|
||||||
|
* @name matchers#toHaveSpyInteractions
|
||||||
|
* @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 ' + typeof 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;
|
||||||
|
if (result.pass) {
|
||||||
|
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(', ');
|
||||||
|
} else {
|
||||||
|
resultMessage =
|
||||||
|
'Expected spy object spies to have been called, ' +
|
||||||
|
'but no spies were called.';
|
||||||
|
}
|
||||||
|
result.message = resultMessage;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return toHaveSpyInteractions;
|
||||||
|
};
|
||||||
|
|
||||||
getJasmineRequireObj().toMatch = function(j$) {
|
getJasmineRequireObj().toMatch = function(j$) {
|
||||||
var getErrorMsg = j$.formatErrorMsg(
|
var getErrorMsg = j$.formatErrorMsg(
|
||||||
'<toMatch>',
|
'<toMatch>',
|
||||||
|
|||||||
22
spec/core/integration/MatchersSpec.js
Normal file → Executable file
22
spec/core/integration/MatchersSpec.js
Normal file → Executable file
@@ -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() {
|
describe('toMatch', function() {
|
||||||
verifyPasses(function(env) {
|
verifyPasses(function(env) {
|
||||||
env.expect('foo').toMatch(/oo$/);
|
env.expect('foo').toMatch(/oo$/);
|
||||||
|
|||||||
115
spec/core/matchers/toHaveSpyInteractionsSpec.js
Executable file
115
spec/core/matchers/toHaveSpyInteractionsSpec.js
Executable file
@@ -0,0 +1,115 @@
|
|||||||
|
describe('toHaveSpyInteractions', function() {
|
||||||
|
it('passes when there are spy interactions', 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes when there are multiple spy interactions', function() {
|
||||||
|
let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions();
|
||||||
|
let spyObj = jasmineUnderTest
|
||||||
|
.getEnv()
|
||||||
|
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||||
|
|
||||||
|
spyObj.spyA();
|
||||||
|
spyObj.spyB();
|
||||||
|
spyObj.spyA();
|
||||||
|
|
||||||
|
let result = matcher.compare(spyObj);
|
||||||
|
expect(result.pass).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when there are no spy interactions', function() {
|
||||||
|
let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions();
|
||||||
|
let spyObj = jasmineUnderTest
|
||||||
|
.getEnv()
|
||||||
|
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||||
|
|
||||||
|
let result = matcher.compare(spyObj);
|
||||||
|
expect(result.pass).toBe(false);
|
||||||
|
expect(result.message).toContain(
|
||||||
|
'Expected spy object spies to have been called'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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()
|
||||||
|
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||||
|
spyObj.otherMethod = function() {};
|
||||||
|
|
||||||
|
spyObj.otherMethod();
|
||||||
|
|
||||||
|
let result = matcher.compare(spyObj);
|
||||||
|
expect(result.pass).toBe(false);
|
||||||
|
expect(result.message).toContain(
|
||||||
|
'Expected spy object spies to have been called'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws an error if a non-object is passed`, function() {
|
||||||
|
let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions();
|
||||||
|
|
||||||
|
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 an error if arguments are passed', function() {
|
||||||
|
let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions();
|
||||||
|
let spyObj = jasmineUnderTest
|
||||||
|
.getEnv()
|
||||||
|
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
matcher.compare(spyObj, 'an argument');
|
||||||
|
}).toThrowError(Error, /Does not take arguments/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if the 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.notSpy = function() {};
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
matcher.compare(spyObj);
|
||||||
|
}).toThrowError(
|
||||||
|
Error,
|
||||||
|
/Expected a spy object with spies, but object has no spies/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
1
src/core/matchers/requireMatchers.js
Normal file → Executable file
1
src/core/matchers/requireMatchers.js
Normal file → Executable file
@@ -27,6 +27,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
|
|||||||
'toHaveBeenCalledTimes',
|
'toHaveBeenCalledTimes',
|
||||||
'toHaveBeenCalledWith',
|
'toHaveBeenCalledWith',
|
||||||
'toHaveClass',
|
'toHaveClass',
|
||||||
|
'toHaveSpyInteractions',
|
||||||
'toMatch',
|
'toMatch',
|
||||||
'toThrow',
|
'toThrow',
|
||||||
'toThrowError',
|
'toThrowError',
|
||||||
|
|||||||
74
src/core/matchers/toHaveSpyInteractions.js
Executable file
74
src/core/matchers/toHaveSpyInteractions.js
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
getJasmineRequireObj().toHaveSpyInteractions = function(j$) {
|
||||||
|
var getErrorMsg = j$.formatErrorMsg(
|
||||||
|
'<toHaveSpyInteractions>',
|
||||||
|
'expect(<spyObj>).toHaveSpyInteractions()'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link expect} the actual (a {@link SpyObj}) spies to have been called.
|
||||||
|
* @function
|
||||||
|
* @name matchers#toHaveSpyInteractions
|
||||||
|
* @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 ' + typeof 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;
|
||||||
|
if (result.pass) {
|
||||||
|
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(', ');
|
||||||
|
} else {
|
||||||
|
resultMessage =
|
||||||
|
'Expected spy object spies to have been called, ' +
|
||||||
|
'but no spies were called.';
|
||||||
|
}
|
||||||
|
result.message = resultMessage;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return toHaveSpyInteractions;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user