Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

expect(spy1).toHaveBeenCalledBefore(spy2) #201

Closed
wants to merge 2 commits into from

6 participants

@mrak

added a matcher to determine if one spy was called before another one during the spec

@jmaicher

+1. That's a useful feature!

@shamansir

Aside from the fact I've created issue #279 on being able to extend spies easy, in most ways checking order may be replaced with (strict variant is in comments):

var first = jasmine.createSpy('first')/*.andCallFake(function() {
    expect(second).not.toHaveBeenCalled();
    expect(third).not.toHaveBeenCalled();
})*/;
var second = jasmine.createSpy('second').andCallFake(function() {
    expect(first).toHaveBeenCalled();
    //expect(third).not.toHaveBeenCalled();
});
var third = jasmine.createSpy('third').andCallFake(function() {
    //expect(first).toHaveBeenCalled();
    expect(second).toHaveBeenCalled();
});

createSpy is easy-replaceable with spyOn

@infews
Owner

This feels a little strange. Do you understand why the calls have to happen in a certain order? This feels a little like a test smell - bending the framework instead of uncovering a potential design problem.

Do you have a use case you can share?

@codeimpossible

+1, i'm surprised that this hasn't been added to jasmine yet.

I have a test case where I am testing a observer and want to assert that the "before" hook is definitively executed before the actual method i am observing.

@shamansir

It is important in cases like testing, if you properly call an API methods in proper order in your application (e.g. HTML5 canvas calls). May be @infews is right for most cases, and it is smarter to test the result of the calls rather their order, but in case of canvas tests it is nearly to impossible.

@slackersoft
Owner

We're looking at doing a refactor of spies and their matchers for 2.1. There is a story in tracker for this, which I've linked back to this pull request. https://www.pivotaltracker.com/story/show/40631373

@slackersoft slackersoft closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
2  lib/jasmine-core/jasmine-html.js
@@ -84,7 +84,7 @@ jasmine.HtmlReporter = function(_doc) {
};
self.reportRunnerResults = function(runner) {
- reporterView.complete();
+ reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
View
81 lib/jasmine-core/jasmine.js
@@ -197,12 +197,12 @@ jasmine.any = function(clazz) {
};
/**
- * Returns a matchable subset of a hash/JSON object. For use in expectations when you don't care about all of the
+ * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
* attributes on the object.
*
* @example
* // don't care about any other attributes than foo.
- * expect(mySpy).toHaveBeenCalledWith(jasmine.hashContaining({foo: "bar"});
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
*
* @param sample {Object} sample
* @returns matchable object for the sample
@@ -293,7 +293,7 @@ jasmine.Spy = function(name) {
};
/**
- * Tells a spy to call through to the actual implementation.
+ * Tells a spy to call through to the actual implemenatation.
*
* @example
* var foo = {
@@ -398,6 +398,7 @@ jasmine.createSpy = function(name) {
spyObj.mostRecentCall.args = args;
spyObj.argsForCall.push(args);
spyObj.calls.push({object: this, args: args});
+ jasmine.getEnv().currentSpec.spyCalls.push({object: this, args: args});
return spyObj.plan.apply(this, arguments);
};
@@ -929,12 +930,12 @@ jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
return a.getTime() == b.getTime();
}
- if (a instanceof jasmine.Matchers.Any) {
- return a.matches(b);
+ if (a.jasmineMatches) {
+ return a.jasmineMatches(b);
}
- if (b instanceof jasmine.Matchers.Any) {
- return b.matches(a);
+ if (b.jasmineMatches) {
+ return b.jasmineMatches(a);
}
if (a instanceof jasmine.Matchers.ObjectContaining) {
@@ -1235,7 +1236,7 @@ jasmine.Matchers.prototype.toEqual = function(expected) {
/**
* toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
* @param expected
- * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ * @deprecated as of 1.0. Use not.toEqual() instead.
*/
jasmine.Matchers.prototype.toNotEqual = function(expected) {
return !this.env.equals_(this.actual, expected);
@@ -1396,6 +1397,51 @@ jasmine.Matchers.prototype.wasNotCalledWith = function() {
};
/**
+ * Matcher that checks if the actual, a spy, was called before the expected, another spy, sometime during the spec
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledBefore = function(expected) {
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ if (!jasmine.isSpy(expected)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(expected) + '.');
+ }
+
+
+ this.message = function() {
+ var messages = [
+ undefined,
+ "Expected " + jasmine.pp(this.actual) + " not to have been called before " + jasmine.pp(expected) + " but it was."
+ ];
+ if (this.actual.callCount === 0) {
+ messages[0] = ("Expected " + jasmine.pp(this.actual) + " to have been called before " + jasmine.pp(expected) + " but it was never called.");
+ } else {
+ messages[0] = ("Expected " + jasmine.pp(this.actual) + " to have been called before " + jasmine.pp(expected) + " but it was called after.");
+ }
+ return messages;
+ };
+
+ if(this.actual.callCount === 0) {
+ return false;
+ }
+ if(expected.callCount === 0) {
+ return true;
+ }
+
+ var me = this;
+ var indexOfSpyCall = function(spy){
+ for(var i = 0; i < me.spec.spyCalls.length; i++){
+ if(me.env.equals_(me.spec.spyCalls[i].object[spy.identity], spy)) return i;
+ }
+ return NaN;
+ }
+
+ return indexOfSpyCall(this.actual) < indexOfSpyCall(expected);
+};
+
+/**
* Matcher that checks that the expected item is an element in the actual Array.
*
* @param {Object} expected
@@ -1408,7 +1454,7 @@ jasmine.Matchers.prototype.toContain = function(expected) {
* Matcher that checks that the expected item is NOT an element in the actual Array.
*
* @param {Object} expected
- * @deprecated as of 1.0. Use not.toNotContain() instead.
+ * @deprecated as of 1.0. Use not.toContain() instead.
*/
jasmine.Matchers.prototype.toNotContain = function(expected) {
return !this.env.contains_(this.actual, expected);
@@ -1476,7 +1522,7 @@ jasmine.Matchers.Any = function(expectedClass) {
this.expectedClass = expectedClass;
};
-jasmine.Matchers.Any.prototype.matches = function(other) {
+jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
if (this.expectedClass == String) {
return typeof other == 'string' || other instanceof String;
}
@@ -1496,7 +1542,7 @@ jasmine.Matchers.Any.prototype.matches = function(other) {
return other instanceof this.expectedClass;
};
-jasmine.Matchers.Any.prototype.toString = function() {
+jasmine.Matchers.Any.prototype.jasmineToString = function() {
return '<jasmine.any(' + this.expectedClass + ')>';
};
@@ -1504,7 +1550,7 @@ jasmine.Matchers.ObjectContaining = function (sample) {
this.sample = sample;
};
-jasmine.Matchers.ObjectContaining.prototype.matches = function(other, mismatchKeys, mismatchValues) {
+jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
@@ -1526,8 +1572,8 @@ jasmine.Matchers.ObjectContaining.prototype.matches = function(other, mismatchKe
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
-jasmine.Matchers.ObjectContaining.prototype.toString = function () {
- return "<jasmine.hashContaining(" + jasmine.pp(this.sample) + ")>";
+jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
+ return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
};
/**
* @constructor
@@ -1669,8 +1715,8 @@ jasmine.PrettyPrinter.prototype.format = function(value) {
this.emitScalar('null');
} else if (value === jasmine.getGlobal()) {
this.emitScalar('<global>');
- } else if (value instanceof jasmine.Matchers.Any) {
- this.emitScalar(value.toString());
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
} else if (typeof value === 'string') {
this.emitString(value);
} else if (jasmine.isSpy(value)) {
@@ -2107,6 +2153,7 @@ jasmine.Spec.prototype.after = function(doAfter) {
jasmine.Spec.prototype.execute = function(onComplete) {
var spec = this;
+ spec.spyCalls = [];
if (!spec.env.specFilter(spec)) {
spec.results_.skipped = true;
spec.finish(onComplete);
@@ -2524,5 +2571,5 @@ jasmine.version_= {
"major": 1,
"minor": 1,
"build": 0,
- "revision": 1299963843
+ "revision": 1331891345
};
View
31 spec/core/MatchersSpec.js
@@ -1103,6 +1103,37 @@ describe("jasmine.Matchers", function() {
});
});
+ describe('toHaveBeenCalledBefore', function(){
+ var before, after, never;
+ beforeEach(function(){
+ before = { fn: function(){}};
+ after = { fn: function(){}};
+ never = { fn: function(){}};
+ spyOn(before, 'fn');
+ spyOn(after, 'fn');
+ spyOn(never, 'fn');
+
+ before.fn();
+ after.fn();
+ });
+ it('should return true if the spy was called before the argument spy', function(){
+ expect(before.fn).toHaveBeenCalledBefore(after.fn);
+ });
+ it('should return true if the spy was called before and after the argument spy', function(){
+ before.fn();
+ expect(before.fn).toHaveBeenCalledBefore(after.fn);
+ });
+ it('should return false if the spy was called after the argument spy', function(){
+ expect(after.fn).not.toHaveBeenCalledBefore(before.fn);
+ });
+ it('should return false if the spy was never called', function(){
+ expect(never.fn).not.toHaveBeenCalledBefore(before.fn);
+ });
+ it('should return true if the spy was called and the argument spy was never called', function(){
+ expect(before.fn).toHaveBeenCalledBefore(never.fn);
+ });
+ });
+
describe("all matchers", function() {
it("should return null, for future-proofing, since we might eventually allow matcher chaining", function() {
expect(match(true).toBe(true)).toBeUndefined();
View
16 spec/core/SpecSpec.js
@@ -53,6 +53,12 @@ describe('Spec', function () {
});
});
+ it('execute should clear jasmine spyCalls upon beginning execution', function () {
+ spec.spyCalls = [{name:'spy one', method: function(){}},{name:'spy two', method: function(){}}];
+
+ spec.execute();
+ expect(spec.spyCalls.length).toEqual(0);
+ });
it('results shows the total number of expectations for each spec after execution', function () {
expect(results.totalCount).toEqual(0);
@@ -113,12 +119,12 @@ describe('Spec', function () {
spec.execute();
var items = results.getItems();
expect(items).toEqual([
- jasmine.any(jasmine.ExpectationResult),
- jasmine.any(jasmine.ExpectationResult),
- jasmine.any(jasmine.MessageResult)
- ]);
+ jasmine.any(jasmine.ExpectationResult),
+ jasmine.any(jasmine.ExpectationResult),
+ jasmine.any(jasmine.MessageResult)
+ ]);
var logResult = items[2];
expect(logResult.values).toEqual(["here's some log message", {key: 'value'}, 123]);
});
});
-});
+});
View
14 spec/core/SpySpec.js
@@ -22,6 +22,20 @@ describe('Spies', function () {
expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']);
});
+ it('calling a spy should push its name to spec.spyCalls', function() {
+ var obj1 = { fn1: function(){}};
+ var obj2 = { fn2: function(){}};
+ spyOn(obj1, 'fn1');
+ spyOn(obj2, 'fn2');
+
+ obj1.fn1();
+ obj2.fn2();
+
+ expect(this.spyCalls.length).toEqual(2);
+ expect(this.spyCalls[0].object).toEqual(obj1);
+ expect(this.spyCalls[1].object).toEqual(obj2);
+ });
+
it('should allow you to view args for a particular call', function() {
var originalFunctionWasCalled = false;
var TestClass = {
View
45 src/core/Matchers.js
@@ -265,6 +265,51 @@ jasmine.Matchers.prototype.wasNotCalledWith = function() {
};
/**
+ * Matcher that checks if the actual, a spy, was called before the expected, another spy, sometime during the spec
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledBefore = function(expected) {
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ if (!jasmine.isSpy(expected)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(expected) + '.');
+ }
+
+
+ this.message = function() {
+ var messages = [
+ undefined,
+ "Expected " + jasmine.pp(this.actual) + " not to have been called before " + jasmine.pp(expected) + " but it was."
+ ];
+ if (this.actual.callCount === 0) {
+ messages[0] = ("Expected " + jasmine.pp(this.actual) + " to have been called before " + jasmine.pp(expected) + " but it was never called.");
+ } else {
+ messages[0] = ("Expected " + jasmine.pp(this.actual) + " to have been called before " + jasmine.pp(expected) + " but it was called after.");
+ }
+ return messages;
+ };
+
+ if(this.actual.callCount === 0) {
+ return false;
+ }
+ if(expected.callCount === 0) {
+ return true;
+ }
+
+ var me = this;
+ var indexOfSpyCall = function(spy){
+ for(var i = 0; i < me.spec.spyCalls.length; i++){
+ if(me.env.equals_(me.spec.spyCalls[i].object[spy.identity], spy)) return i;
+ }
+ return NaN;
+ }
+
+ return indexOfSpyCall(this.actual) < indexOfSpyCall(expected);
+};
+
+/**
* Matcher that checks that the expected item is an element in the actual Array.
*
* @param {Object} expected
View
1  src/core/Spec.js
@@ -162,6 +162,7 @@ jasmine.Spec.prototype.after = function(doAfter) {
jasmine.Spec.prototype.execute = function(onComplete) {
var spec = this;
+ spec.spyCalls = [];
if (!spec.env.specFilter(spec)) {
spec.results_.skipped = true;
spec.finish(onComplete);
View
1  src/core/base.js
@@ -398,6 +398,7 @@ jasmine.createSpy = function(name) {
spyObj.mostRecentCall.args = args;
spyObj.argsForCall.push(args);
spyObj.calls.push({object: this, args: args});
+ jasmine.getEnv().currentSpec.spyCalls.push({object: this, args: args});
return spyObj.plan.apply(this, arguments);
};
View
2  src/version.js
@@ -2,5 +2,5 @@ jasmine.version_= {
"major": 1,
"minor": 1,
"build": 0,
- "revision": 1320442951
+ "revision": 1331891345
};
Something went wrong with that request. Please try again.