1

I'm new to Jasmine, so apologies if this is a dumb question.

I have a test like this...

it("should calculate factorial 5", function() {
  expect(MathsUtils.fact(5)).toBe(120);
});

This works fine. My fact function throws an exception if you pass it a negative number, so I tried to test that with the following...

it("should throw an exception when passed -1", function() {
  expect(MathsUtils.fact(-1)).toThrow("n! does not exist for negative numbers");
});

However, this failed. After some searching, I discovered that if I change this test to look like this...

it("should throw an exception when passed -1", function() {
  expect(function() { MathsUtils.fact(-1); }).toThrow("n! does not exist for negative numbers");
});

...it passes. However, if I change my first test in a similar manner...

it("should calculate factorial 5", function() {
  expect(function() { MathsUtils.fact(5); }).toBe(120);
});

...it fails.

Why do I need a different syntax for the two tests? Neither seems to work for the other.

As I said, I'm new with Jasmine, so if this is covered in the docs, please point me in the right direction, as I couldn't see any explanation.

Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106
  • 1
    that's how it was implemented, you just have to use it like that. Have you tried `toThrowError` – 0.sh Jul 04 '17 at 21:05
  • 3
    Because, **per the documentation**, *"The 'toThrow' matcher is for testing if a function throws an exception"*. Otherwise the exception would be thrown *before* `expect` actually gets called. – jonrsharpe Jul 04 '17 at 21:05
  • @VictoryOsikwemhe I tried toThrowError, but that also failed. Do you have a link to the docs? Thanks – Avrohom Yisroel Jul 04 '17 at 21:11

2 Answers2

2

In the example

expect(MathsUtils.fact(-1))

The fact is evaluated before expect is called, and hence it cannot catch the exception,

where

expect(function(){MathsUtils.fact(-1)})

expect is doing the execution and can catch the exception as what is passed is a function pointer and not an already evaluated value

Soren
  • 14,402
  • 4
  • 41
  • 67
  • Ah, that's probably what @jonrsharpe meant. I was about to ask him to explain it. Do you have a link to the docs for this? I found them a bit hard to navigate. Thanks – Avrohom Yisroel Jul 04 '17 at 21:13
  • 1
    No reference -- this is a JavaScript thing and not really related to Jasmine -- anything that is inside a function/closure is evaluated `later` by the receiver – Soren Jul 04 '17 at 21:15
  • OK, I guess this is where my lack of knowledge of the finer points of Javascript start to show. I'm a C# programmer by day, doing some web stuff as extras. Do you know where I'd find more info about this. I'd like to learn how this works. Thanks again – Avrohom Yisroel Jul 04 '17 at 21:19
  • 1
    https://stackoverflow.com/questions/111102/how-do-javascript-closures-work – Soren Jul 04 '17 at 21:21
  • Thanks, plenty to read there! I thought I had a handle on closures, but it looks like there's a lot more to learn. Thanks again – Avrohom Yisroel Jul 04 '17 at 21:24
0

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 ...

  1. Create the Expectation which is defined here

  2. The "toThrow" matcher defines a compare() method which is mixed into the Expectation object.

tl;dr:

  1. 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.

try-catch-finally
  • 7,436
  • 6
  • 46
  • 67