Custom object formatters allow users to customize how an object is
stringified in matcher failure messages. This can already be done by
adding a `jasmineToString` method to the objects in question. But
it's not always desirable or possible to do that, particularly when
objects of a given "type" do not inherit from a specific prototype.
For instance, suppose a web service returns a list of foos that are
deserialized from JSON, e.g.:
{ fooId: 42, /* more properties */ }
The only way to define `jasmineToString` on those is by writing code to
add it to each instance at runtime. But a custom object formatter can
recognize that the object it's looking at is a foo and format it
accordingly:
jasmine.addCustomObjectFormatter(function(obj) {
if (typeof obj.fooId !== 'number') {
return undefined;
}
return '[Foo with ID ' + obj.fooId + ']';
});
Unlike `jasmineToString`, custom object formatters are scoped to a
particular spec or suite and don't require any changes to the code
under test.
This will allow us to add support for custom object formatters, which
will be a per-runable resource like custom matchers, by injecting them
into the pretty-printer.
This makes it easier to write high quality matchers and asymmetric equality
testers, and is also a step toward supporting custom object formatters.
Previously, Jasmine passed custom object formatters as the second argument
to matcher factories and as and the second argument to asymmetric equality
testers' `asymmetricMatch` method. Matchers and asymmetric equality testers
were responsible for passing the custom object formatters to methods like
`matchersUtil#equals`:
function toEqual(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
// ...
result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder);
And:
ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) {
// ...
for (var i = 0; i < this.sample.length; i++) {
var item = this.sample[i];
if (!j$.matchersUtil.contains(other, item, customTesters)) {
return false;
}
}
With this change, that is no longer necessary. Matchers and asymmetric
equality testers can ignore the existence of custom equality testers and
still fully support them:
function toEqual(util) {
return {
compare: function(actual, expected) {
// ...
result.pass = util.equals(actual, expected, diffBuilder);
And:
ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
// ...
for (var i = 0; i < this.sample.length; i++) {
var item = this.sample[i];
if (!matchersUtil.contains(other, item)) {
return false;
}
}
The old interfaces are still supported, for now, but will be deprecated
in a future commit and removed in the next major release after that.
In addition to making matchers and custom equality testers simpler,
this change sets the stage for adding support for custom object
formatters. Those will be architecturally similar to custom equality
testers, and by injecting a `MatchersUtil` instance everywhere we can
add them without requiring user code to pass them around as used to be
the case with custom object formatters.
When I first saw it(), I was wondering if the name "it" is
an abbreviation of anything. After some search, I finally
realized that the name is only a pronoun. Therefore, I
think it's worthwhile to include it in the documentation.
Turns this output:
Expected $[0].foo = Object({ a: 4, b: 5 }) to equal <jasmine.objectContaining(Object({ a: 1, c: 3 }))>.
into this:
Expected $[0].foo.a = 4 to equal 1.
Expected $[0].foo.c = undefined to equal 3.
And turns this output:
Expected spy jasmineDone to have been called with:
[ ... snipped very long expected call ]
but actual calls were:
[ ... snipped very long actual call ]
Call 0:
Expected $[0] = Object({ overallStatus: 'failed', totalTime: 1, incompleteReason: undefined, order: Order({ random: true, seed: '88732', sort: Function }), failedExpectations: [ Object({ matcherName: 'toBeResolved', passed: false, message: 'Suite "a suite" ran a "toBeResolved" expectation after it finished.
Did you forget to return or await the result of expectAsync?', error: undefined, errorForStack: Error, actual: [object Promise], expected: [ ], globalErrorType: 'lateExpeztation' }) ], deprecationWarnings: [ ] }) to equal <jasmine.objectContaining(Object({ failedExpectations: [ <jasmine.objectContaining(Object({ passed: false, globalErrorType: 'lateExpectation', message: 'Suite "a suite" ran a "toBeResolved" expectation after it finished.
Did you forget to return or await the result of expectAsync?', matcherName: 'toBeResolved' }))> ] }))>.
into this:
Expected spy jasmineDone to have been called with:
[ ... snipped very long expected call ]
but actual calls were:
[ ... snipped very long actual call ]
Call 0:
Expected $[0].failedExpectations[0].globalErrorType = 'lateExpeztation' to equal 'lateExpectation'.
This makes it easier to see where each failure message begins and ends.
Before:
Some context: a
multiline
message
After:
Some context:
a
multiline
message
It's very easy to forget to `await` or `return` the promise returned
from `expectAsync`. When that happens, the expectation failure will
occur after the spec or suite's result has been reported to reporters,
and the failure will typically not be shown to the user. This change
adds a top-level suite failure in that case, similar to the way we
report unhandled exceptions or promise rejections that occur after the
runable completes. Adding the error at the top level gives us the best
chance of getting in before the set of failures we add it to is sent
to reporters.
See #1752.
If the actual value of a test was a string, this was matching against arrays
that contained the strings. This was due to the use of the contains matcher,
which against string looks for substrings, when it was intended to look for
array elements.
The first number is the error message in HTML5 browser, which does not include
the call stack. The error instance allows logging the complete call stack in
reporters.
There are now multiple ways to do async functions, and callbacks
are probably the least common in new code, so the message should
be more general rather than referring to callbacks.
The global window error handler is used to handle errors thrown from within asynchronous functions and tests. The first parameter is the error; the fifth parameter is the full error object including the stacktrace. Searching for the first occurrence of an error instance to work with browsers, which may not comply with the HTML5 standard.
This breaks each call out onto its own line, so that it's much easier to
see where each call starts and how they differ. E.g. previously the output
would be:
Expected spy foo to have been called with [ 'bar', 'baz', 'qux' ] but actual calls were [ [ 42, 'wibble' ], [ 'bar' 'qux' ], [ 'grault '] ]
Now it's:
Expected spy foo to have been called with:
[ 'bar', 'baz', 'qux' ]
but actual calls were:
[ 42, 'wibble' ],
[ 'bar' 'qux' ],
[ 'grault '].