如何在Jasmine中编写一个期望抛出“Error”的测试?

604

我正在尝试编写一个Jasmine测试框架的测试,期望出现错误。目前我正在使用来自GitHub的Jasmine Node.js集成

在我的Node.js模块中,我有以下代码:

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

现在我尝试编写一个测试,期望出现这个错误:
describe('my suite...', function() {
    [..]
    it('should not parse foo', function() {
    [..]
        expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));
    });
});

我也尝试了Error()和其他变体,但就是无法弄清楚如何让它工作。


4
如果不使用匿名函数来传递参数到被测试的函数,可以尝试使用Function.bind方法:https://dev59.com/5Wkx5IYBdhLWcg3wAvqn#13233194 - Danyal Aytekin
12个回答

944

尝试使用匿名函数:

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

或者使用Lambda表达式:

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

你应该将一个函数传递到expect(...)调用中。你的错误代码:

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

正在尝试实际调用parser.parse(raw),以便将结果传递给expect(...)


29
如果不需要传递参数,你也可以直接将函数传递给 expectexpect(parser.parse).toThrow(...) - SubmittedDenied
76
实用提示:你可以简单地调用 expect(blah).toThrow()。没有参数意味着检查是否会抛出异常,不需要进行字符串匹配。另请参阅:https://dev59.com/5Wkx5IYBdhLWcg3wAvqn#9525172。 - Jess
2
在我看来,当用匿名函数包装测试时,测试的意图更加明显。此外,当你需要传递参数使目标函数抛出异常时,这种方式在所有测试中保持一致。 - Beez
11
一般情况下,这样做是无效的!如果parser.parse使用了this,那么不带上上下文传递它将产生意想不到的结果。你可以传递parser.parse.bind(parser),但说实话……一个匿名函数会更加优雅。 - mhelvens
没有赞成,因为有 911 票,但你有我的暗投票 :thumbsup: - Michahell
再次回到这个答案,谢谢! - MichaelEr

91

您正在使用:

expect(fn).toThrow(e)

但是,如果您查看函数注释(期望为字符串):

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) {

我想你应该这样写(使用lambda - 匿名函数):

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

下面的示例证实了这一点:

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

道格拉斯·克罗克福特 强烈推荐使用这种方式,而不是使用 "throw new Error()"(原型方式):

throw {
   name: "Error",
   message: "Parsing is not possible"
}

3
实际上,查看代码时,toThrow可以接受异常对象或字符串。例如,请查看它对expected.message的调用。 - Pete Hodgson
1
似乎允许字符串作为字符串没有消息属性的副作用。 - mpapis
19
如果你抛出的是一个物体而不是一个错误(就像在底部的例子中一样),那么在支持它的浏览器中将不会得到堆栈跟踪。 - kybernetikos
2
@kybernetikos 令人惊讶的是,这并不完全正确;如果您抛出一个非 Error 的对象(http://jsfiddle.net/k1mxey8j/),Chrome 控制台仍会打印出堆栈跟踪。然而,您抛出的对象当然不会有 .stack 属性,如果您想设置自动化错误报告,则可能很重要。 - Mark Amery

37

如前所述,在测试中需要传递函数到 toThrow, 因为它是你正在描述的函数:"我期望这个函数会抛出x"

expect(() => parser.parse(raw))
  .toThrow(new Error('Parsing is not possible'));
如果使用Jasmine-Matchers,也可以在适当的情况下使用以下方法之一;
// I just want to know that an error was
// thrown and nothing more about it
expect(() => parser.parse(raw))
  .toThrowAnyError();
或者
// I just want to know that an error of 
// a given type was thrown and nothing more
expect(() => parser.parse(raw))
  .toThrowErrorOfType(TypeError);

4
在Jasmine 2.5中,它是expect(foo).toThrowError(TypeError);。请参考:https://jasmine.github.io/2.5/introduction - Benny Code

31
一种比创建仅用于包装另一个函数的匿名函数更加优雅的解决方案是使用 ES5bind 函数。 bind 函数会创建一个新函数,当调用该函数时,其 this 关键字设置为提供的值,并在调用新函数时提供的任何参数之前提供给定的参数序列。
与其这样写: expect(function () { parser.parse(raw, config); } ).toThrow("Parsing is not possible"); 可以考虑这样写: expect(parser.parse.bind(parser, raw, config)).toThrow("Parsing is not possible"); bind 语法允许您测试具有不同 this 值的函数,并且在我看来使测试更易读。另请参阅:

Jasmine的toThrow匹配器是否需要将参数包装在匿名函数中?


25

我用以下代码替换了Jasmine的toThrow匹配器,它允许你按照异常的名称属性或其消息属性进行匹配。对我来说,这使得测试更易于编写且更加健壮,因为我可以使用以下代码:

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

然后使用以下方式进行测试:

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

这样做让我可以后期调整异常信息,而不会破坏测试结果,因为重要的是它抛出了预期类型的异常。

这是替代 toThrow 的方法,它允许这样做:

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;
};

1
实际上,应该使用现代的Jasmine库将其实现为自定义匹配器。我做了类似的事情,并创建了一个名为toThrowErrorNamed的自定义匹配器。 - FakeFootball

12

我知道这样会增加更多的代码量,但你也可以这样做:

try
    Do something
    @fail Error("should send a Exception")
catch e
    expect(e.name).toBe "BLA_ERROR"
    expect(e.message).toBe 'Message'

9
在我的情况下,抛出错误的函数是异步的,所以我按照这个做法。
await expectAsync(asyncFunction()).toBeRejected();
await expectAsync(asyncFunction()).toBeRejectedWithError(...);

6

针对喜欢使用CoffeeScript的用户:

expect( => someMethodCall(arg1, arg2)).toThrow()

5
it('it should fail', async () => {
    expect.assertions(1);

    try {
        await testInstance.doSomething();
    }
    catch (ex) {
        expect(ex).toBeInstanceOf(MyCustomError);
    }
});

1
需要进行解释。例如,什么是想法/要点?来自帮助中心:“...始终说明您提出的解决方案为何适当以及其工作原理”。请通过编辑(更改)您的答案做出回应,而不是在此处进行评论(不要添加“编辑:”、“更新:”或类似的内容-答案应该看起来像是今天编写的)。 - Peter Mortensen

4

对我来说,发布的解决方案没有起作用,它一直抛出这个错误:

错误:期望函数抛出异常。

后来我意识到,我期望抛出错误的函数是一个 async 函数,并且期望 promise 被拒绝,然后抛出错误,这就是我在我的代码中所做的:

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

我在我的测试中就是这样做的,而且它起作用了:

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');
        });
}));

谢谢你。我非常困惑,但是你的评论让我完全明白了。我使用新的 expectAsync 解决了这个问题 https://jasmine.github.io/api/3.3/async-matchers.html - Benjamin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接