Mocha / Chai s'attendent à ce que les erreurs ne soient pas détectées

259

J'ai des problèmes pour obtenir Chai expect.to.throw travaille dans un test pour mon application node.js. Le test continue d'échouer sur l'erreur levée, mais si j'encapsule le cas de test pour essayer d'attraper et d'affirmer l'erreur capturée, cela fonctionne.

Ne expect.to.throwfonctionne pas comme je pense que cela devrait ou quelque chose?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

L'échec:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.
do Ré Mi
la source

Réponses:

340

Vous devez passer une fonction à expect. Comme ça:

expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));

La façon dont vous le faites, vous passez à expectla suite de l' appel model.get('z'). Mais pour tester si quelque chose est lancé, vous devez passer une fonction à expect, qui expects'appellera. La bindméthode utilisée ci-dessus crée une nouvelle fonction qui, lorsqu'elle est appelée, appelle model.getavec thisset à la valeur de modelet le premier argument à 'z'.

Une bonne explication de bindpeut être trouvée ici .

Louis
la source
J'ai réussi une fonction non? modell'instance a une fonction appelée get que j'ai passée / appelée dans expect.
doremi
Non, voyez l'explication que j'ai ajoutée pendant que vous écriviez votre commentaire.
Louis
47
Oof. Pourquoi les documents ( chaijs.com/api/bdd/#throw ) ne démontrent-ils pas cette utilisation de bind? Il semble que le scénario de test le plus courant to.throwconsiste à tester une condition particulière dans une fonction, ce qui nécessite d'appeler cette fonction avec l'état / les arguments non valides. (D'ailleurs ... pourquoi les liens profonds de chaijs.com ne sont-ils pas réellement des liens profonds?)
ericsoco
Lorsque vous passez certains paramètres qui ne devraient pas être lancés, le test est quand même un succès.
Alexandros Spyropoulos
6
Notez que cela ne fonctionnera pas (à partir de septembre 2017) pour les fonctions asynchrones: voir github.com/chaijs/chai/issues/882#issuecomment-322131680 et la discussion associée.
ChrisV
175

Comme le dit cette réponse , vous pouvez également simplement envelopper votre code dans une fonction anonyme comme celle-ci:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
twiz
la source
7
Cela ne fonctionne pas pour les appels de fonction asynchrones. Supposons que model.get soit async qui renvoie la promesse. Cependant, il renvoie une erreur. Si j'essaye l'approche ci-dessus, c'est "Timing out" car nous devons notifier "done" à mocha. En même temps, je ne peux pas essayer expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); car il n'y a pas de méthode de notification.
Anand N
@AnandN Si je comprends votre problème, il semble que vous ayez juste besoin de refactoriser votre code pour gérer l'erreur. Est-ce que l'erreur non gérée dans la fonction asynchrone sera également un problème dans votre application actuelle?
twiz
2
Merci twiz pour ta réponse. Nous travaillons dans un environnement intégré, le module using prend en charge les exceptions. Donc, le problème est lorsque nous essayons d'exécuter des cas de tests unitaires. Enfin, nous avons utilisé l'approche ci-dessous pour le faire fonctionner catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N
1
Bonne solution sauf lorsque vous utilisez l' thisintérieur de la fonction à appeler. C'est alors .bindla bonne façon de procéder.
rabbitco
@AnandN L'appel de fonction asynchrone ne lance pas , il rejette s. Pour référence future, chai-as-promised gère cela très bien.
user5532169
85

Et si vous utilisez déjà ES6 / ES2015, vous pouvez également utiliser une fonction de flèche. C'est essentiellement la même chose que d'utiliser une fonction anonyme normale mais plus courte.

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
Daniel T.
la source
Il peut y avoir un problème avec cela car les fonctions de flèche prennent leur ampleur environnante pourthis
Eric Hodonsky
1
@Relic Oui, très vrai. Cela peut également être un gros avantage des fonctions fléchées. Les fonctions fléchées «héritent» thisde la portée dans laquelle elles ont été créées. Souvent, cela peut être un avantage, car cela évite d'avoir à bindinsérer thismanuellement des fonctions dans leur objet.
Stijn de Witt
@StijndeWitt ce n'est pas un avantage ou un inconvénient, c'est un contrôle de portée et intentionnel. C'est en fait du sucre de syntaxe à utiliser bindet toujours lié à thisl'étendue parent. Mon intention dans le commentaire était seulement de m'assurer que les lecteurs étaient au courant d'une chute potentielle.
Eric Hodonsky
1
@Relic Oui, je suis d'accord avec vous. Il peut être utilisé à un avantage et peut être une bonne raison d'utiliser une fonction flèche.
Stijn de Witt
75

Cette question a de nombreux doublons, y compris des questions ne mentionnant pas la bibliothèque d'assertions Chai. Voici les bases rassemblées ensemble:

L'assertion doit appeler la fonction, au lieu de l'évaluer immédiatement.

assert.throws(x.y.z);      
   // FAIL.  x.y.z throws an exception, which immediately exits the
   // enclosing block, so assert.throw() not called.
assert.throws(()=>x.y.z);  
   // assert.throw() is called with a function, which only throws
   // when assert.throw executes the function.
assert.throws(function () { x.y.z });   
   // if you cannot use ES6 at work
function badReference() { x.y.z }; assert.throws(badReference);  
   // for the verbose
assert.throws(()=>model.get(z));  
   // the specific example given.
homegrownAssertThrows(model.get, z);
   //  a style common in Python, but not in JavaScript

Vous pouvez rechercher des erreurs spécifiques à l'aide de n'importe quelle bibliothèque d'assertions:

Nœud

  assert.throws(() => x.y.z);
  assert.throws(() => x.y.z, ReferenceError);
  assert.throws(() => x.y.z, ReferenceError, /is not defined/);
  assert.throws(() => x.y.z, /is not defined/);
  assert.doesNotThrow(() => 42);
  assert.throws(() => x.y.z, Error);
  assert.throws(() => model.get.z, /Property does not exist in model schema./)

Devrait

  should.throws(() => x.y.z);
  should.throws(() => x.y.z, ReferenceError);
  should.throws(() => x.y.z, ReferenceError, /is not defined/);
  should.throws(() => x.y.z, /is not defined/);
  should.doesNotThrow(() => 42);
  should.throws(() => x.y.z, Error);
  should.throws(() => model.get.z, /Property does not exist in model schema./)

Chai Expect

  expect(() => x.y.z).to.throw();
  expect(() => x.y.z).to.throw(ReferenceError);
  expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
  expect(() => x.y.z).to.throw(/is not defined/);
  expect(() => 42).not.to.throw();
  expect(() => x.y.z).to.throw(Error);
  expect(() => model.get.z).to.throw(/Property does not exist in model schema./);

Vous devez gérer les exceptions qui «échappent» au test

it('should handle escaped errors', function () {
  try {
    expect(() => x.y.z).not.to.throw(RangeError);
  } catch (err) {
    expect(err).to.be.a(ReferenceError);
  }
});

Cela peut sembler déroutant au premier abord. Comme faire du vélo, il clique pour toujours une fois qu'il clique.

Charles Merriam
la source
16

exemples de doc ...;)

parce que vous comptez sur le thiscontexte:

  • qui est perdu lorsque la fonction est invoquée par .throw
  • il n'y a aucun moyen de savoir ce que cela est censé être

vous devez utiliser l'une de ces options:

  • encapsuler l'appel de méthode ou de fonction à l'intérieur d'une autre fonction
  • lier le contexte

    // wrap the method or function call inside of another function
    expect(function () { cat.meow(); }).to.throw();  // Function expression
    expect(() => cat.meow()).to.throw();             // ES6 arrow function
    
    // bind the context
    expect(cat.meow.bind(cat)).to.throw();           // Bind
Michal Miky Jankovský
la source
C'est aussi ainsi que je le fais. Je trouve que la mise en œuvre d'ES6 est de loin la plus lisible
relief.melone
1

Une autre implémentation possible, plus lourde que la solution .bind (), mais qui aide à faire valoir que expect () nécessite une fonction qui fournit un thiscontexte à la fonction couverte, vous pouvez utiliser un call(), par exemple,

expect(function() {model.get.call(model, 'z');}).to.throw('...');

SeanOlson
la source
0

J'ai trouvé un bon moyen de le contourner:

// The test, BDD style
it ("unsupported site", () => {
    The.function(myFunc)
    .with.arguments({url:"https://www.ebay.com/"})
    .should.throw(/unsupported/);
});


// The function that does the magic: (lang:TypeScript)
export const The = {
    'function': (func:Function) => ({
        'with': ({
            'arguments': function (...args:any) {
                return () => func(...args);
            }
        })
    })
};

C'est beaucoup plus lisible que mon ancienne version:

it ("unsupported site", () => {
    const args = {url:"https://www.ebay.com/"}; //Arrange
    function check_unsupported_site() { myFunc(args) } //Act
    check_unsupported_site.should.throw(/unsupported/) //Assert
});
Dani-Br
la source