This is an addendum to the comments in Soren's answer.
expect
is doing the execution and can catch the exception as what is passed is a function pointer and not an already evaluated value
Take a look under the hood of Jasmine 2.6:
When you call the expect()
method several things happen ...
Create the Expectation
which is defined here
The "toThrow" matcher defines a compare()
method which is mixed into the Expectation
object.
tl;dr:
In toThrow()
the actual
value, along with the expected
value, was passed by the compare()
call. The value is evaluated as function call within a try/catch block
.
function toThrow(util) {
return {
compare: function(actual, expected) {
var result = { pass: false },
threw = false,
thrown;
if (typeof actual != 'function') {
throw new Error(getErrorMsg('Actual is not a Function'));
}
try {
actual();
} catch (e) {
threw = true;
thrown = e;
}
if (!threw) {
result.message = 'Expected function to throw an exception.';
return result;
}
// ...
}
}
}
Why do I need a different syntax for the two tests? Neither seems to work for the other.
After reading toThrow()
this should be clearer.
While a primitive type like a number or a string passed to, say, the toBe()
matcher can be evaluated immediately, the "value" of an exception (that are the threw
and thrown
variables up there) is created when thrown and when not thrown. Thus the macher should hold the control of the execution.
Note that
it("should throw", function() {
expect(someFunction()).toThrow("meh");
});
will semi-work since someFunction()
may throw and the suite will catch that exception and report a spec failure when the closure passed to it()
is being executed.
But since in this case neither expect()
nor toThrow()
are called, it's not able to check for the message nor the type (like toThrowError()
does). It's only able to detect that the spec failed.
This small example which can run in NodeJS or a browser console should make it even clearer ...
You can also make your own expectation function:
// Expectation constructor
function Expectation(actual, reporter) {
this.actual = actual;
this.reporter = reporter;
}
// matcher "mixed in"
Expectation.prototype.toThrow = function(expected) {
try {
this.actual();
// No exception thrown, this state is wrong
this.reporter(false, "Expected " + this.actual.name + " to throw but it did not throw");
} catch(e) {
// This exception was expected, this state is correct
if (e.message !== expected) {
// But the message was wrong
this.reporter(false, "Expected " + this.actual.name + " to throw " + expected + " but it threw " + e.message);
} else {
// And its message was correct
this.reporter(true);
}
}
};
// Helper to report (log)
function report(passed, msg) {
if (!passed) {
console.error(msg);
} else {
console.log("passed");
}
}
// Helper to create expectation
function expect(actual) {
return new Expectation(actual, report);
}
> function correctError() { throw new Error("meh!"); }
> expect(correctError).toThrow("meh!");
< passed
> function badMessageError() { throw new Error("hehe"); }
> expect(badMessageError).toThrow("meh!");
< Expected badMessageError to throw meh! but it threw hehe
> function noError() { /* no action */ }
> expect(noError).toThrow("meh!");
< Expected noError to throw but it did not throw
Per your comment
[...] I'm a C# programmer by day, [...]
there's a nice C# answer to a similar question that implements the same behaviour like Jasmine.