Permalink
Browse files

backporting framework-agnostic branch's nifty feature to automaticall…

…y generate friendly assertion messages when none are provided, along with associated tests
  • Loading branch information...
mmonteleone committed Mar 29, 2011
1 parent e40987a commit c64ebe4dbbaea481b9ca6901f0a7aa726ea07b73
Showing with 133 additions and 12 deletions.
  1. +5 −4 README.markdown
  2. +50 −6 pavlov.js
  3. +78 −2 spec/pavlov.specs.js
View
@@ -13,7 +13,7 @@ Pavlov extends JavaScript testing framework [QUnit][1] with a rich, higher-level
* Nested examples (describes)
* Cascading befores and afters (setups and teardowns)
* Generative row tests
-* Fluent, extendable, assertions
+* Fluent, extendable, assertions with intelligent messages
* Spec stubbing
* Simplified async support
* Non-DOM-polluting
@@ -268,8 +268,9 @@ Creates a spec (test) to occur within an example (decribe)
When not passed fn, creates a spec-stubbing fn which asserts fail "Not Implemented"
*Parameters*
-* specification (String) - Description of what "it" "should do"
-* fn (Function) - Optional function containing a test to assert that it does indeed do it (optional)
+
+ * specification (String) - Description of what "it" "should do"
+ * fn (Function) - Optional function containing a test to assert that it does indeed do it (optional)
*Example*
@@ -329,7 +330,7 @@ A few Examples:
#### Built-in Assertions
-Most are self-explanatory. Message parameter is always optional.
+Most are self-explanatory. Message parameter is always optional. When a message is not provided, one is automatically generated based on combining the letter-cased assertion method name with serialized versions of the expected and actual parameters. So, `assert(5).isNotEqualTo(4)` generates a default message of `asserting 5 is not equal to 4`.
* assert(actual).equals(expected, message);
* assert(actual).isEqualTo(expected, message);
View
@@ -230,11 +230,10 @@
actual();
ok(!true, message);
} catch(e) {
- if(arguments.length > 1) {
- ok(e === expectedErrorDescription, message);
- } else {
- ok(true, message);
- }
+ // so, this bit of weirdness is basically a way to allow for the fact
+ // that the test may have specified a particular type of error to catch, or not.
+ // and if not, e would always === e.
+ ok(e === (expectedErrorDescription || e), message);
}
}
};
@@ -250,6 +249,41 @@
var assertHandler = function(value) {
this.value = value;
};
+
+ /**
+ * Naive display formatter for objects which wraps the objects'
+ * own toString() value with type-specific delimiters.
+ * [] for array
+ * "" for string
+ * Does not currently go nearly detailed enough for JSON use,
+ * just enough to show small values within test results
+ * @param {Object} obj object to format
+ * @returns naive display-formatted string representation of the object
+ */
+ var format = function(obj) {
+ if(typeof obj === 'undefined') {
+ return "";
+ } else if(Object.prototype.toString.call(obj) === "[object Array]") {
+ return '[' + obj.toString() + ']';
+ } else if(Object.prototype.toString.call(obj) === "[object Function]") {
+ return "function()";
+ } else if(typeof obj == "string") {
+ return '"' + obj + '"';
+ } else {
+ return obj;
+ }
+ };
+
+ /**
+ * transforms a camel or pascal case string
+ * to all lower-case space-separated phrase
+ * @param {string} value pascal or camel-cased string
+ * @returns all-lower-case space-separated phrase
+ */
+ var letterCase = function(value) {
+ return value.replace(/([A-Z])/g,' $1').toLowerCase();
+ };
+
/**
* Appends assertion methods to the assertHandler prototype
* For each provided assertion implementation, adds an identically named
@@ -263,7 +297,17 @@
// by pre-pending assertHandler's current value to args
var args = makeArray(arguments);
args.unshift(this.value);
- fn.apply(this, args);
+
+ // if no explicit message was given with the assertion,
+ // then let's build our own friendly one
+ if(fn.length === 2) {
+ args[1] = args[1] || 'asserting ' + format(args[0]) + ' ' + letterCase(name);
+ } else if(fn.length === 3) {
+ var expected = format(args[1]);
+ args[2] = args[2] || 'asserting ' + format(args[0]) + ' ' + letterCase(name) + (expected ? ' ' + expected : expected);
+ }
+
+ fn.apply(this, args);
};
});
};
View
@@ -620,7 +620,7 @@ QUnit.specify("Pavlov", function() {
});
// verify correct arguments would have been passed to qunit
- assert(passedArgs).isSameAs([true,undefined]);
+ assert(passedArgs).isSameAs([true,'asserting function() throws exception']);
});
it("should pass false to qunit's ok() when function does not throw exception", function(){
@@ -634,7 +634,7 @@ QUnit.specify("Pavlov", function() {
});
// verify correct arguments would have been passed to qunit
- assert(passedArgs).isSameAs([false,undefined]);
+ assert(passedArgs).isSameAs([false,'asserting function() throws exception']);
});
it("should pass true to qunit's ok() when function throws exception with expected description", function(){
@@ -701,6 +701,82 @@ QUnit.specify("Pavlov", function() {
});
});
+
+ describe("that have provided messages", function(){
+ it("should display those messages", function(){
+ var gtArgs, ltArgs;
+ QUnit.specify.extendAssertions({
+ isGreaterThan: function(actual, expected, message) {
+ gtArgs = $.makeArray(arguments);
+ }
+ });
+ assert(4).isGreaterThan(2,"some message");
+ assert(gtArgs).isSameAs([4,2,"some message"]);
+ });
+ });
+
+ describe("that do not have provided messages", function(){
+ it("should generate messages using letter-cased assertion name and serialized expected/actuals", function(){
+ var gtArgs, ltArgs;
+ QUnit.specify.extendAssertions({
+ isGreaterThan: function(actual, expected, message) {
+ gtArgs = $.makeArray(arguments);
+ }
+ });
+ assert(4).isGreaterThan(2);
+ assert(gtArgs).isSameAs([4,2,"asserting 4 is greater than 2"]);
+ });
+ describe("when the values are arrays", function(){
+ it("should properly serialize", function(){
+ var gtArgs, ltArgs;
+ QUnit.specify.extendAssertions({
+ hasLengthOf: function(actual, expected, message) {
+ gtArgs = $.makeArray(arguments);
+ }
+ });
+ assert(['a','b','c']).hasLengthOf(3);
+ assert(gtArgs).isSameAs([['a','b','c'],3,"asserting [a,b,c] has length of 3"]);
+ });
+ });
+ describe("when the values are functions", function(){
+ it("should properly serialize", function(){
+ var gtArgs, ltArgs;
+ QUnit.specify.extendAssertions({
+ isAFunction: function(actual, message) {
+ gtArgs = $.makeArray(arguments);
+ }
+ });
+ var helloFn = function() { alert('hello'); };
+ assert(helloFn).isAFunction();
+ assert(gtArgs).isSameAs([helloFn,"asserting function() is a function"]);
+ });
+ });
+ describe("when the values are strings", function(){
+ it("should properly serialize", function(){
+ var gtArgs, ltArgs;
+ QUnit.specify.extendAssertions({
+ isAStringWithLengthOf: function(actual, expected, message) {
+ gtArgs = $.makeArray(arguments);
+ }
+ });
+ assert("test string").isAStringWithLengthOf(11);
+ assert(gtArgs).isSameAs(["test string",11,"asserting \"test string\" is a string with length of 11"]);
+ });
+ });
+ describe("when the values are primitives", function(){
+ given([4,5],[false,true],[3.14,2.718])
+ .it("should properly serialize", function(a,b){
+ var gtArgs, ltArgs;
+ QUnit.specify.extendAssertions({
+ isNotTheSameLiteralValueAs: function(actual, expected, message) {
+ gtArgs = $.makeArray(arguments);
+ }
+ });
+ assert(a).isNotTheSameLiteralValueAs(b);
+ assert(gtArgs).isSameAs([a,b,("asserting " + a.toString() + " is not the same literal value as " + b.toString())]);
+ });
+ });
+ });
});
});

0 comments on commit c64ebe4

Please sign in to comment.