Como escrever um teste que espera que um erro seja lançado no Jasmine?


490

Estou tentando escrever um teste para o Jasmine Test Framework que espera um erro. No momento, estou usando uma integração do Jasmine Node.js do GitHub .

No meu módulo Nó, tenho o seguinte código:

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

Agora eu tento escrever um teste que espera este erro:

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

Eu tentei também Error()e algumas outras variantes e simplesmente não consigo descobrir como fazê-lo funcionar.


4
Para passar argumentos para a função que está sendo testado, sem usar uma função anônima, tente Function.bind: stackoverflow.com/a/13233194/294855
— Danyal Aytekin

Respostas:


802

você deve passar uma função para a expect(...)chamada. O código que você tem aqui:

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

está tentando realmente ligar parser.parse(raw) na tentativa de passar o resultado expect(...),

Tente usar uma função anônima:

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

28
Se você não precisa passar argumentos também, você também pode simplesmente passar a função para esperar:expect(parser.parse).toThrow(...)
— SubmittedDenied

60
Dica útil: você pode simplesmente ligar expect(blah).toThrow(). Nenhum argumento significa verificar se ele é lançado. Nenhuma correspondência de sequência é necessária. Veja também: stackoverflow.com/a/9525172/1804678
— Jess

1
Na minha opinião, é mais óbvio quanto à intenção do teste ao envolver uma função anônima. Além disso, ele permanece consistente entre todos os testes quando, por exemplo, você precisa passar parâmetros para a função de destino para executá-lo.
— Beez

10
@SubmittedDenied: Isso não funciona em geral! Se parser.parseusar this, passá-lo sem contexto produzirá resultados inesperados. Você poderia passar parser.parse.bind(parser), mas honestamente ... uma função anônima seria mais elegante.
— Mhelvens

2
@LanceKind desculpe necro, mas a razão pela qual você precisa passar uma função é que um valor seria avaliado e lançaria uma exceção antes mesmo de ser passada para a expectativa.
— 1gLassitude

68

Você está usando:

expect(fn).toThrow(e)

Mas se você der uma olhada no comentário da função (o esperado é 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) {

Suponho que você provavelmente deva escrever assim (usando a função lambda - anonymous):

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

Isso é confirmado no exemplo a seguir:

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

Douglas Crockford recomenda enfaticamente essa abordagem, em vez de usar "throw new Error ()" (forma de prototipagem):

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

3
Na verdade, olhar para o código toThrow terá um objeto de exceção / ou / uma string. Confira as chamadas que ele está fazendo para o expect.message, por exemplo.
— Pete Hodgson

1
Ele emendas para permitir a string como um efeito colateral da corda não ter propriedade da mensagem
— mpapis

1
Muito obrigado que funcionou. Ainda aceitei a resposta de Pete, porque a resposta dele deixou mais claro para mim que eu precisava usar uma lambda. Ainda +1 :-) Obrigado!
— Echox

16
Se você lançar um objeto em vez de um erro (como no exemplo na parte inferior), não receberá um rastreamento de pilha nos navegadores que o suportam.
— precisa saber é o seguinte

2
@kybernetikos surpreendentemente, não é inteiramente verdade; você ainda terá um rastreamento de pilha impresso no console do Chrome se lançar um não Error( jsfiddle.net/k1mxey8j ). No entanto, é claro que seu objeto lançado não terá a .stackpropriedade, o que pode ser importante se você deseja configurar o relatório de erros automatizado .
— Mark Amery

24

Uma solução mais elegante do que criar uma função anônima, cujo único objetivo é envolver outra, é usar a bindfunção es5 . A função de ligação cria uma nova função que, quando chamada, tem sua thispalavra-chave definida como o valor fornecido, com uma sequência de argumentos fornecida antes de qualquer fornecida quando a nova função é chamada.

Ao invés de:

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

Considerar:

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

A sintaxe de ligação permite testar funções com thisvalores diferentes e, na minha opinião, torna o teste mais legível. Consulte também: https://stackoverflow.com/a/13233194/1248889


23

Substituo o comparador toThrow do Jasmine pelo seguinte, que permite corresponder na propriedade name da exceção ou na propriedade message. Para mim, isso torna os testes mais fáceis de escrever e menos quebradiços, pois eu posso fazer o seguinte:

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

e depois teste com o seguinte:

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

Isso me permite ajustar a mensagem de exceção posteriormente, sem interromper os testes, quando o importante é que ela lançou o tipo de exceção esperado.

Esta é a substituição do toThrow que permite:

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

4
Uma boa abordagem, mas {name: '...', message: '...'} é um objeto de erro apropriado no JavaScript?
— Marc

1
Bom comentário @Marc. Você está certo, a propriedade name não é padrão. developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/… , mas isso é tão errado?
— Jess

4
@Jake! Eu encontrei uma maneira melhor !!!! Você pode simplesmente ligar expect(blah).toThrow(). Nenhum argumento significa verificar se ele é lançado. Nenhuma correspondência de sequência é necessária. Veja também: stackoverflow.com/a/9525172/1804678
— Jess

5
Obrigado Jess - isso é verdade, mas pode haver outro erro, como um TypeError, e meu teste será aprovado incorretamente, mascarando um bug real.
— Jake #

4
Agora você também pode usar um RegEx como argumento para toThrow ().
— Tony O'Hagan

21

Como mencionado anteriormente, uma função precisa ser passada para toThrowa função que você está descrevendo em seu teste: "Espero que essa função gere x"

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

Se você estiver usando o Jasmine-Matchers, também poderá usar um dos seguintes itens quando eles se adequarem à situação;

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

ou

// I just want to know that an error of 
// a given type was thrown and nothing more
expect(() => parser.parse(raw))
  .toThrowErrorOfType(TypeError);

3
É expect(foo).toThrowError(TypeError);em Jasmine 2,5: jasmine.github.io/2.5/introduction
— Benny Neugebauer

9

Eu sei que é mais código, mas você também pode fazer:

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

Tenho tendência a gostar do aspecto 'autodocumentados' para isso ... torna muito aparente que você unidade testar um estado de erro
— JRulle

6

Para os amantes do café

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

3

Para qualquer um que ainda esteja enfrentando esse problema, para mim a solução postada não funcionou e continuou lançando esse erro: Mais Error: Expected function to throw an exception. tarde, percebi que a função que eu esperava lançar um erro era uma função assíncrona e esperava promessa de ser rejeitado e, em seguida, lançar erro e é isso que eu estava fazendo no meu código:

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

e foi o que fiz no meu teste e funcionou:

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

Obrigado por isso. Fiquei muito confuso, mas seu comentário faz todo o sentido. Eu corrigiu o problema usando o novo expectAsync jasmine.github.io/api/3.3/async-matchers.html
— Benjamin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.