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.

echox
fonte
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"));
Pete Hodgson
fonte
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"
}
Andrzej Śliwa
fonte
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

Jonathan Gawrych
fonte
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;
};
Jake
fonte
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);
Jamie Mason
fonte
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'
tolbard
fonte
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()
fernandohur
fonte
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');
                });
            }));
arifaBatool
fonte
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