How can I write a test which expects an 'Error' to be thrown in Jasmine?
Asked Answered
S

12

609

I'm trying to write a test for the Jasmine Test Framework which expects an error. At the moment I'm using a Jasmine Node.js integration from GitHub.

In my Node.js module I have the following code:

throw new Error("Parsing is not possible");

Now I try to write a test which expects this error:

describe('my suite...', function() {
    [..]
    it('should not parse foo', function() {
    [..]
        expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));
    });
});

I tried also Error() and some other variants and just can't figure out how to make it work.

Shackle answered 10/11, 2010 at 12:57 Comment(1)
To pass arguments to the function being tested, without using an anonymous function, try Function.bind: https://mcmap.net/q/65495/-does-jasmine-39-s-tothrow-matcher-require-the-argument-to-be-wrapped-in-an-anonymous-functionDarlington
E
948

Try using an anonymous function instead:

expect( function(){ parser.parse(raw); } ).toThrow(new Error("Parsing is not possible"));

Or using a lambda:

expect( () => parser.parse(raw) ).toThrow(new Error("Parsing is not possible"));

you should be passing a function into the expect(...) call. Your incorrect code:

// incorrect:
expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));
    

is trying to actually call parser.parse(raw) in an attempt to pass the result into expect(...),

Electrode answered 10/11, 2010 at 13:13 Comment(6)
If you don't need to pass arguments too, you can also just pass the function to expect: expect(parser.parse).toThrow(...)Use
Helpful tip: You can simply call expect(blah).toThrow(). No arguments means check to see that it throws at all. No string matching required. See also: https://mcmap.net/q/65495/-does-jasmine-39-s-tothrow-matcher-require-the-argument-to-be-wrapped-in-an-anonymous-functionDagnydago
In my opinion, it is more obvious as to the intent of the test when wrapping in an anonymous function. Also, it remains consistent among all tests when, for example, you have to pass parameters to the target function to make it throw.Assembled
@SubmittedDenied: This doesn't work in general! If parser.parse uses this, passing it without context will produce unexpected results. You could pass parser.parse.bind(parser), but honestly... an anonymous function would be more elegant.Jarodjarosite
not upvoted because there are 911 votes, but you got my shadow upvote :thumbsup:Huberty
Coming back to this answer for the who knows which time Thanks again!Nimbus
K
91

You are using:

expect(fn).toThrow(e)

But if you'll have a look on the function comment (expected is string):

294 /**
295  * Matcher that checks that the expected exception was thrown by the actual.
296  *
297  * @param {String} expected
298  */
299 jasmine.Matchers.prototype.toThrow = function(expected) {

I suppose you should probably write it like this (using lambda - anonymous function):

expect(function() { parser.parse(raw); } ).toThrow("Parsing is not possible");

This is confirmed in the following example:

expect(function () {throw new Error("Parsing is not possible")}).toThrow("Parsing is not possible");

Douglas Crockford strongly recommends this approach, instead of using "throw new Error()" (prototyping way):

throw {
   name: "Error",
   message: "Parsing is not possible"
}
Kashakashden answered 10/11, 2010 at 13:6 Comment(4)
Actually looking at the code toThrow will happily take either an exception object /or/ a string. Check out the calls it is making to expected.message for example.Electrode
It seams to allow string as a side effect of string having no message propertyUzzi
If you throw an object rather than an Error (as in your example at the bottom), then you won't get a stack trace in the browsers that support it.Roulers
@Roulers surprisingly, not entirely true; you'll still get a stack trace printed in the Chrome console if you throw a non-Error (jsfiddle.net/k1mxey8j). However, your thrown object of course won't have the .stack property, which may be important if you want to set up automated error reporting.Acerbity
I
38

As mentioned previously, a function needs to be passed to toThrow as it is the function you're describing in your test: "I expect this function to throw x"

expect(() => parser.parse(raw))
  .toThrow(new Error('Parsing is not possible'));

If using Jasmine-Matchers you can also use one of the following when they suit the situation;

// I just want to know that an error was
// thrown and nothing more about it
expect(() => parser.parse(raw))
  .toThrowAnyError();

or

// I just want to know that an error of 
// a given type was thrown and nothing more
expect(() => parser.parse(raw))
  .toThrowErrorOfType(TypeError);
Insatiable answered 24/1, 2017 at 8:51 Comment(1)
It's expect(foo).toThrowError(TypeError); in Jasmine 2.5: jasmine.github.io/2.5/introductionIndication
R
31

A more elegant solution than creating an anonymous function whose sole purpose is to wrap another, is to use ES5's bind function. The bind function creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Instead of:

expect(function () { parser.parse(raw, config); } ).toThrow("Parsing is not possible");

Consider:

expect(parser.parse.bind(parser, raw, config)).toThrow("Parsing is not possible");

The bind syntax allows you to test functions with different this values, and in my opinion makes the test more readable. See also:

Does Jasmine's toThrow matcher require the argument to be wrapped in an anonymous function?

Road answered 5/2, 2015 at 17:30 Comment(0)
M
26

I replace Jasmine's toThrow matcher with the following, which lets you match on the exception's name property or its message property. For me this makes tests easier to write and less brittle, as I can do the following:

throw {
   name: "NoActionProvided",
   message: "Please specify an 'action' property when configuring the action map."
}

and then test with the following:

expect (function () {
   .. do something
}).toThrow ("NoActionProvided");

This lets me tweak the exception message later without breaking tests, when the important thing is that it threw the expected type of exception.

This is the replacement for toThrow that allows this:

jasmine.Matchers.prototype.toThrow = function(expected) {
  var result = false;
  var exception;
  if (typeof this.actual != 'function') {
    throw new Error('Actual is not a function');
  }
  try {
    this.actual();
  } catch (e) {
    exception = e;
  }
  if (exception) {
      result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected) || this.env.equals_(exception.name, expected));
  }

  var not = this.isNot ? "not " : "";

  this.message = function() {
    if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
      return ["Expected function " + not + "to throw", expected ? expected.name || expected.message || expected : " an exception", ", but it threw", exception.name || exception.message || exception].join(' ');
    } else {
      return "Expected function to throw an exception.";
    }
  };

  return result;
};
Matriarchy answered 28/7, 2011 at 10:15 Comment(1)
Really this should be implementing this as a custom matcher with the modern Jasmine library. I did something similar and created a custom matcher called toThrowErrorNamedSaith
R
12

I know that is more code, but you can also do:

try
    Do something
    @fail Error("should send a Exception")
catch e
    expect(e.name).toBe "BLA_ERROR"
    expect(e.message).toBe 'Message'
Resupine answered 19/2, 2014 at 3:16 Comment(0)
B
10

In my case, the function throwing an error was async, so I followed this:

await expectAsync(asyncFunction()).toBeRejected();
await expectAsync(asyncFunction()).toBeRejectedWithError(...);
Brunswick answered 23/7, 2020 at 17:40 Comment(0)
K
6

For CoffeeScript lovers:

expect( => someMethodCall(arg1, arg2)).toThrow()
Kilah answered 15/2, 2014 at 17:41 Comment(0)
M
6
it('it should fail', async () => {
    expect.assertions(1);

    try {
        await testInstance.doSomething();
    }
    catch (ex) {
        expect(ex).toBeInstanceOf(MyCustomError);
    }
});
Medallist answered 10/8, 2021 at 19:30 Comment(1)
An explanation would be in order. E.g., what is the idea/gist? From the Help Center: "...always explain why the solution you're presenting is appropriate and how it works". Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Differentiable
A
4

For me, the posted solution didn't work and it kept on throwing this error:

Error: Expected function to throw an exception.

I later realised that the function which I was expecting to throw an error was an async function and was expecting the promise to be rejected and then throw an error and that's what I was doing in my code:

throw new Error('REQUEST ID NOT FOUND');

And that’s what I did in my test and it worked:

it('Test should throw error if request not found', willResolve(() => {
    const promise = service.getRequestStatus('request-id');
        return expectToReject(promise).then((err) => {
            expect(err.message).toEqual('REQUEST NOT FOUND');
        });
}));
Agone answered 15/2, 2018 at 11:9 Comment(1)
Thanks for this. I was very confused, but your comment makes perfect sense. I fixed the issue using the new expectAsync jasmine.github.io/api/3.3/async-matchers.htmlZarger
W
0

I ran into similar problem (and this question on Stackoverflow) when was trying to check that async function throws, and found that first answer works great after I create a following helper function (below is TypeScript, removing types will give you plain JavaScript):


export function toPromise(funcOrPromise: ()=>Promise| Promise): Promise{
  let promise = funcOrPromise instanceOf Promise ? funcOrPromise : funcOrPromise();
  return new Promise((resolve, reject) => promise.then( r=>resolve(r)).catch(e=>reject(e)));
}

To be used as this:

asyncExpect(toPromise(asyncFunc)).toBeRejectedWithError(/error text/);

or

asyncExpect(toPromise(asyncFunc())).toBeRejectedWithError(/error text/);
Whim answered 17/4, 2023 at 23:43 Comment(0)
A
0

Inspired by Juan Rada's answer, here is a "poor man's" approach, using only expect(a).toBe(b):

it('Throws an error when doing xyz', function() {
  let caughtError = '';
  try {
    xyz();
  } catch (e) {
    caughtError = e.message;
  }
  expect( caughtError ).toBe( 'Parsing is not possible' );
});
Antiquity answered 2/5, 2023 at 22:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.