Jest: Contexte lisible par l'homme pour les attentes

Créé le 21 oct. 2016  ·  76Commentaires  ·  Source: facebook/jest

S'il y a plusieurs attentes dans un seul it , il semble actuellement impossible de déterminer quelle attente a réellement échoué sans faire référence à l'échec avec les numéros de ligne dans votre code.

test('api works', () => {
    expect(api()).toEqual([]) // api without magic provides no items
    expect(api(0)).toEqual([]) // api with zero magic also provides no items
    expect(api(true)).toEqual([1,2,3]) // api with magic enabled provides all items
})

Quelle attente a échoué ? Le premier ou le second ?

image

Ce serait bien s'il y avait un contexte lisible par l'homme qui indiquait immédiatement quelle attente a échoué et ce que la sortie de l'attente signifie réellement en termes humains, sans avoir à trouver le numéro de ligne en haut de la trace de la pile et à le mapper à la code.


Comparez l'équivalent tape ci-dessous. Ignorez que la bande ne s'écoule pas après le premier échec d'assertion. tape imprime un message lisible par l'homme au-dessus de chaque échec attendu, vous permettant de savoir exactement quel test a échoué sans revenir au fichier de test.

Notez que cela repousse également le bruit lisible par l'homme à la fin de la ligne dans la source de test, où vous pouvez quand même écrire un commentaire.

test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
})

image


Il semble que la seule façon d'attacher des informations lisibles par l'homme aux erreurs avec jest est de tout envelopper dans un it supplémentaire qui est inutilement verbeux IMO.

describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Idéalement, on pourrait attacher un contexte lisible par l'homme à la fin du expect d'une manière ou d'une autre.

par exemple

Message de contexte en tant que paramètre facultatif supplémentaire pour les méthodes d'assertion :

test('api works', () => {
    expect(api()).toEqual([], 'api without magic provides no items')
    expect(api(0)).toEqual([], 'api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3], 'api with magic enabled provides all items')
})


Ou un message contextuel sous forme de chaîne .because ou .why ou .comment ou .t ou quelque chose :

test('api works', () => {
    expect(api()).toEqual([]).because('api without magic provides no items')
    expect(api(0)).toEqual([]).because('api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3]).because('api with magic enabled provides all items')
})

Alternativement, ce serait peut-être encore mieux si jest pouvait simplement lire le fichier et imprimer la ligne de code source réelle sur laquelle se trouve l'attente elle-même.

Commentaire le plus utile

D'après cette discussion et ce référentiel, je pense qu'une belle et sémantique serait:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

L'utilisation est similaire à celle de because , mais elle a plus de sens sémantiquement.

Si vous aimez cela, je pourrais peut- être élaborer un PR en ajoutant la fonctionnalité since .

Tous les 76 commentaires

Hey! Donc, nous avions l'habitude d'avoir cela dans Jasmine, mais nous avons constaté que sur des milliers de fichiers de test sur FB, personne ne l'utilisait. Donc, pour l'instant, nous imprimons un joli message d'erreur avec des informations approximatives et une trace de pile qui conduira à l'attente (comme dans votre capture d'écran). Je suis d'accord que nous pourrions imprimer la ligne qui lance mais assez souvent l'assertion est longue de plusieurs lignes :

expect(a).toEqual({
  …
});

donc cela n'aurait pas l'air si bon et nous devions utiliser un analyseur pour analyser le JS et extraire les informations pertinentes (et réduire les longues lignes) ou quelque chose de similaire pour le rendre joli.

Personnellement, je pense que nous montrons suffisamment d'informations pour l'instant, mais heureux de reconsidérer. Si vous avez des idées pour quelque chose qui n'est pas très complexe mais qui ajoute plus de contexte, ce qui aide à résoudre les problèmes plus rapidement, faites-le moi savoir.

nous avions en fait cela dans Jasmine mais nous avons constaté que sur des milliers de fichiers de test sur FB, personne ne l'utilisait

@cpojer donc le modèle est d'envelopper chaque assertion dans un it ? et/ou simplement faire confiance aux numéros de ligne ?

Est-il possible que ce schéma ait été adopté moins parce qu'il est meilleur ou moins bon, mais plus juste par cohérence avec les tests existants ? ou peut-être ne sachant pas que la fonctionnalité existe ? Je ne savais pas que c'était à Jasmine.

Je suis d'accord que nous pourrions imprimer la ligne qui lance mais assez souvent l'affirmation est longue de plusieurs lignes

La refactorisation en une seule ligne pourrait encourager plus d'informations sémantiques dans l'assertion ? Peut-être?

const adminUser = {
  …
}
expect(a).toEqual(adminUser);

Personnellement, je pense que nous montrons suffisamment d'informations pour l'instant, mais heureux de reconsidérer

L'exemple ci-dessus montre qu'il est difficile de découvrir exactement quelle assertion a échoué à moins que vous n'ajoutiez des wrappers verbeux (IMO) autour de tout. Cela est particulièrement vrai dans un environnement transpilé où les numéros de ligne de sourcemap ne sont pas toujours exacts. Je crois qu'une compréhension rapide et précise s'est cassée et où est important, tout comme des tests concis.

Si vous avez des idées pour quelque chose qui n'est pas très complexe mais qui ajoute plus de contexte, ce qui aide à résoudre les problèmes plus rapidement, faites-le moi savoir.

J'ai fait quelques suggestions ci-dessus:

Vous cherchez quelque chose de plus simple ou de différent ?

Salut @timoxley ! nous avons déjà pensé à ajouter quelque chose comme ça.

donc le problème avec la première option est que certains matchers ont des arguments facultatifs, ce qui complique les choses.

par exemple ici dans le second cas on ne saura pas si l'argument est une proximité ou un message d'erreur

expect(555).toBeCloseTo(111, 2, 'reason why');
expect(555).toBeCloseTo(111, 'reason why');

la deuxième suggestion ne fonctionnera pas car le matcher lancera dès que quelque chose ne répondra pas aux attentes

expect(1).toBe(2)/* will throw here */.because('reason');

nous pourrions attacher la raison avant que le matcher ne s'exécute, comme ceci :

expect(1).because('reason').toBe(2);
// or 
because('reason').expect(1).toBe(2);

mais cette API n'a pas vraiment l'air si bonne.

une autre option serait d'ajouter un deuxième argument à expect

expect(1, 'just because').toBe(2);

mais c'est à peu près la même chose que l'option précédente.

La raison pour laquelle je pense que ce n'est pas très utile est que les ingénieurs ne veulent pas perdre de temps à écrire des tests. Tout ce que nous faisons pour leur rendre la tâche plus difficile ne fera que conduire à de pires tests.

Dans le passé, la meilleure solution consistait en fait à créer des matchers personnalisés. Nous introduirons expect.extend dans la prochaine version de Jest et cela vous permettra de créer facilement des matchers comme :

expect(a).toEqualMySpecificThing(…)

ce qui devrait vous permettre d'écrire des messages d'échec plus expressifs. J'ai vu cela beaucoup utilisé dans des projets comme Relay. Voir tous les matchers : https://github.com/facebook/relay/blob/master/src/tools/__mocks__/RelayTestUtils.js#L281

Fermeture pour cause d'inactivité mais heureux de rouvrir s'il y a de bonnes idées.

@cpojer @dmitriiabramov excuses pour le retard.

Un deuxième argument à expect ou enchaîner une raison avec .because serait génial. Que faut-il faire pour que cela se produise ou non ?

les ingénieurs ne veulent pas perdre de temps à écrire des tests

@cpojer D'accord ! En plus de ne pas vouloir perdre de temps à déboguer des tests, c'est exactement pourquoi je pense qu'une API moins détaillée avec plus de contexte d'échec serait préférable.

Pour certains nombres concrets, en utilisant l'exemple simple de mon commentaire ci-dessus, pour obtenir des assertions équivalentes + contexte avec bande, Jest exige que le programmeur écrive presque le double de la quantité de passe-partout cérémoniel :

  • 1,8x les lignes (6 contre 11)
  • 2x l'indentation (1 vs 2)
  • 2x les parenthèses/bouclés (24 vs 48) !

Cela pourrait être amélioré !

// tape
test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
  t.end()
})

// jest
describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Mise à jour : je suppose que vous pourriez écrire les tests de plaisanterie sur une seule ligne avec des flèches :

// jest
describe('api works', () => {
  test('api without magic provides no items', () => expect(api()).toEqual([]))
  test('api with zero magic also provides no items', () => expect(api(0)).toEqual([]))
  test('api with magic enabled provides all items', () => expect(api(true)).toEqual([1,2,3]))
})

Cela fait des lignes plus longues, mais améliore quelque peu les statistiques que nous avons comparées plus tôt :

  • 0,8x les lignes (6 vs 5)
  • 1x l'indentation (1 contre 1)
  • 1,75x les parenthèses/boucles (24 vs 42)

Cependant, je pense que le fait d'avoir la description du test au début de la ligne, sans saut de ligne, rend plus difficile l'analyse visuelle de la logique car elle place la "viande" du test, c'est-à-dire les assertions réelles, à une position de colonne arbitraire.

L'analyse du code est plus importante que la lecture de la description du test, qui n'est en fait qu'un commentaire glorifié. C'est pourquoi personne n'écrit de commentaires en début de ligne .ex ce serait de la folie sadomasochiste :

/* api without magic provides no items */ expect(api()).toEqual([])
/* api with zero magic also provides no items */ expect(api(0)).toEqual([])
/* api with magic enabled provides all items */ expect(api(true)).toEqual([1,2,3])

Idéalement, tout le code d'assertion s'alignerait parfaitement dans la même colonne afin qu'il soit facilement analysé par un humain. Sur la base de cette réflexion, j'opterais fortement pour la forme finale .because plutôt que la suggestion alternative d'un deuxième argument à expect .

Merci d'avoir entretenu la conversation. Notez que vous n'avez pas non plus besoin du bloc de description, ce qui rend les choses plus petites. .because ne fonctionnera malheureusement pas car lorsque le matcher lancera (ce qui se produit avant que .because ne soit appelé), nous n'aurons aucun moyen d'extraire le nom.

Utilisez alors le dernier argument de chaque fonction matcher ?

Cela ne fonctionnerait que si nous l'ajoutions comme deuxième argument de expect .
Nous ne pouvons pas l'ajouter comme dernier argument pour chaque fonction matcher en raison de l'ambiguïté
par exemple

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

Fermeture pour cause d'inactivité mais heureux de rouvrir s'il y a de bonnes idées.

@cpojer on ne sait pas si la voie du jasmin (et des autres) de expect(value).toBe(something, 'because message') a été exclue ?

Un exemple teste des trucs redux-saga/redux-observable où vous testez une machine d'état . Il est très utile d'avoir un message descriptif sur l'état dans lequel il a échoué. Cet exemple est artificiel, donc les descriptions le sont aussi.

@jayphelps nous n'utilisons plus la méthode du jasmin depuis que nous avons réécrit tous les matchers de jasmin

@dmitriiabramov désolé ma question n'était pas claire. Le jasmin _way_ de le faire a-t-il été décidé qu'il doit être rajouté ? Faire la même chose qu'ils autorisent.

@jayphelps comme je l'ai déjà dit, cela ne fonctionnera pas pour tous les matchers à cause de l'ambiguïté.

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

et sing jest matchers peut être étendu avec des packages tiers je ne pense pas que ce soit une bonne idée de jouer avec la liste d'arguments

l'option la plus propre est probablement de l'avoir comme deuxième argument de expect , car il prend toujours exactement un argument.

expect(123, 'jest because').toEqual(123);

Je ne sais pas si nous voulons surcharger l'API. Nous ne l'avons presque jamais utilisé dans les suites de tests Facebook, et pour les cas particuliers, je pense qu'il est plus simple de définir simplement un nouveau test :

beforeEach(someSharedSetup);
test('reason or description', () => expect(1).toBe(1));

c'est juste quelques lignes de plus :)

Ou vous pouvez même le mettre dans un autre appel describe() .

@dmitriiabramov Les cas ennuyeux sont lorsque vous créez un état, comme dans une machine à états pour les sagas, les épopées, etc. Chaque test nécessite les changements d'état précédents, les isoler nécessite une tonne de duplication sans aucun gain AFAIK.

it('stuff', () => {
  const generator = incrementAsync();

  expect(generator.next().value).toBe(
    call(delay, 1000)
  );

  expect(generator.next().value).toBe(
    put({ type: 'INCREMENT' })
  );

  expect(generator.next()).toBe(
    { done: true, value: undefined }
  );
});

Ou vous pouvez même le mettre dans un autre appel describe().

Pouvez-vous élaborer cela? L'imbrication des appels de description AFAIK était juste pour diviser les titres de section, les tests sont toujours exécutés simultanément, n'est-ce pas ?

Les suites de tests (fichiers) s'exécutent simultanément, contrairement aux appels test() .

je commence à penser que quelque chose comme

test('111' () => {
  jest.debug('write something only if it fails');
  expect(1).toBe(2);
});

peut être une chose

D'après cette discussion et ce référentiel, je pense qu'une belle et sémantique serait:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

L'utilisation est similaire à celle de because , mais elle a plus de sens sémantiquement.

Si vous aimez cela, je pourrais peut- être élaborer un PR en ajoutant la fonctionnalité since .

Veuillez implémenter un moyen simple de le faire. Je ne l'utilise pas très souvent, mais surtout pour les tests plus compliqués, il est utile de savoir exactement ce qui ne va pas sans avoir à creuser.

S'il vous plaît, ne dites pas "réécrivez vos suites de tests pour être plus simples". La seule chose que les ingénieurs détestent plus que _écrire_ des suites de tests, c'est _réécrire_ des suites de tests.

Une autre proposition que j'ai vue quelque part, j'oublie où c'était dans ce même numéro, avec une explication de la raison pour laquelle cela ne fonctionnerait pas. Je devrais probablement dormir un peu :)

J'ai une démonstration "prototype" simple qui fonctionne, j'aurais besoin d'implémenter la récursivité maintenant. Il s'agit d'un wrapper fin utilisant des proxys autour des variables globales, puis sur chaque méthode. Cependant, les proxys ne sont pas pris en charge par les anciens navigateurs et ne peuvent pas être remplis en plusieurs fois, ils peuvent donc ne pas être acceptables pour Jest. Voici la structure générale du wrapper :

const since = (text) => {
  return new Proxy(global, {
    get: (orig, key) => {
      return (...args) => {
        try {
          const stack = orig[key](...args);
          return new Proxy(stack, {
            get: (orig, key) => {
              return (...args) => {
                try {
                  const ret = orig[key](...args);

                  // ... implement recursion here

                } catch (err) {
                  console.log('2', key, text, err);
                  throw err;
                }
              }
            }
          });
        } catch (err) {
          console.log('1', key, text, err);
          throw err;
        }
      };
    }
  });
};

Il existe trois options réalistes :

  • Cette méthode est acceptable et doit donc être ajoutée à la bibliothèque Jest principale. Je le nettoie et crée un PR.
  • Creusez plus profondément dans Jest et modifiez la bibliothèque principale. Beaucoup de travail, donc je ne ferais rien jusqu'à ce que certains membres disent quelque chose de manière semi-officielle sur cette question/direction.
  • Terminez-le de cette façon et publiez-le sous forme de package. Indésirable car il n'est pas facilement détectable.

Edit : le voir en action :

describe('Test', () => {
  it('works', () => {
    since('It fails!').expect('a').toEqual('b');
  });
});

Vous avez besoin d'un contexte d'attente pour que les résultats des tests soient sains lorsque vous avez des tests non triviaux. Les tests dans le monde réel ne seraient pas toujours aussi simples.

N'oubliez pas les correspondances personnalisées - elles cachent la complexité des calculs. Mais lorsque le test échoue, masquer cette complexité n'est pas ce que vous voulez car vous voulez un maximum d'informations sur l'échec. Le contexte d'attente vous permet de fournir ce contexte manuellement. Pas idéal, je suppose, une sorte de contexte automatique serait mieux, mais c'est la seule façon que j'ai vue jusqu'à présent.

Lorsque j'ai cassé quelque chose et que cela échoue, avec Jest, je dois le déboguer manuellement ou ajouter une journalisation ou quoi que ce soit _modifications._ Ce qui est beaucoup moins pratique que de simplement regarder les résultats des tests.
Dans Jasmine par exemple, nous avons la possibilité d'imprimer un contexte pour donner plus de sens à l'échec.
Dans JUnit, le framework de test le plus populaire de Java, nous avons exactement la même fonctionnalité.

Désolé si je me trompe, mais je ne vois aucun contre-argument _technologique_ à cette fonctionnalité ici. Et des choses comme "cela ne devrait pas être ajouté parce que ça n'aura pas l'air bien" sont tout simplement ridicules.

Pouvons-nous rouvrir ? Même jest.debug() comme suggéré par @aaronabramov ci-dessus me serait utile.

This:

it('has all the methods', () => {
    since('cookie is a method', () => expect(reply.cookie).toBeDefined());
});

can be supported by adding this:


// setupTestFrameworkScriptFile.js
// http://facebook.github.io/jest/docs/configuration.html#setuptestframeworkscriptfile-string
global.since = (explanation, fn) => {
    try {
        fn();
    } catch(e) {
        e.message = explanation + '\n' + e.message;
        throw e;
    }
};

De plus, jasmine-custom-message ressemble à ce qui est demandé :

describe('test', function() {
  it('should be ok', function() {
    since(function() {
      return {'tiger': 'kitty'};
    }).
    expect(3).toEqual(4); // => '{"tiger":"kitty"}'
  });
});

Est-il prévu de le rouvrir ? Il semble qu'il y ait eu des doublons de ce problème récemment. Je cherche également à afficher un message personnalisé lorsque le test échoue.

Hmm .. C'est aussi quelque chose que j'ai sur ma liste de souhaits. Après avoir lu ce fil, je peux comprendre la réponse concernant l'utilisation de Jest sur Facebook et ne pas vouloir avoir d'impact sur votre propre flux de travail, mais il y a eu quelques suggestions qui n'interféreraient pas avec les tests existants et ajouteraient la fonctionnalité que plusieurs autres aimeraient ai (moi y compris).

Que faudrait-il pour que le 2ème argument attend() ou le format since() soit accepté comme PR ? Je suis prêt à donner du temps pour aider avec ça.

Je viens de lire le fil et je vois de bons arguments des deux côtés. Je veux vraiment qu'un mécanisme fournisse un message d'erreur personnalisé pour la même raison que @timoxley a initialement publiée. En ce moment je fais quelque chose comme ça :

import assert from 'assert'
import chalk from 'chalk'

test('api works', () => {
  assert.deepEqual(
    api(),
    [],
    chalk.red('api without magic provides no items')
  )
  assert.deepEqual(
    api(0),
    [],
    chalk.red('api with zero magic also provides no items')
  )
  assert.deepEqual(
    api(true),
    [1, 2, 3],
    chalk.red('api with magic enabled provides all items')
  )
})

Cela fonctionne en fait remarquablement bien, mais j'aimerais éviter d'avoir à utiliser de la craie pour obtenir la couleur rouge (impressions sans couleur sinon) et je préférerais que cela soit pris en charge par expect .

Je ne me soucie pas vraiment de la façon dont il est mis en œuvre honnêtement. Mais voici une alternative à since juste pour jeter quelque chose d'autre au cas où d'autres le préféreraient :

const expectWithMessage = expect.withMessage(
  'api with magic enabled provides all items'
)
expectWithMessage(api(true)).toEqual([1, 2, 3])

// could be rewritten like
expect
  .withMessage('api with magic enabled provides all items')(api(true))
  .toEqual([1, 2, 3])

Je ne suis pas certain d'aimer ça mieux que since . Je suis bon avec n'importe quoi, j'aimerais vraiment avoir ça :)

Oh, et pour répondre au commentaire:

La raison pour laquelle je pense que ce n'est pas très utile est que les ingénieurs ne veulent pas perdre de temps à écrire des tests. Tout ce que nous faisons pour leur rendre la tâche plus difficile ne fera que conduire à de pires tests.

Je suis d'accord que nous ne voulons pas compliquer la rédaction des tests. C'est pourquoi ce serait un changement additif. Donc, les gens qui ne veulent pas "perdre de temps" à rendre leurs tests plus faciles à déboguer, ils peuvent simplement ignorer les messages utiles, mais ensuite ils entreront dans une base de code contenant des messages utiles comme celui-ci, puis ils remercieront le ingénieur qui a pris le temps d'expliquer un peu l'assertion :wink:

Salut @cpojer des mises à jour à ce sujet ?

Jest fonctionne très bien pour moi, sauf pour ce problème ... Pour le moment, j'ai du mal à corriger une assertion ratée dans une boucle for comme
expectationsArray.forEach(expectation => expect(...))

Il est difficile de déterminer exactement quelles attentes échouent sans un message d'erreur personnalisé (à moins que je ne le fasse mal ..?)

Merci

@mj-airwallex vous êtes bon pour envelopper les attentes avec un test dans une boucle for par exemple :

const expectationsArray = [[0, 'a'], [1, 'b']];

expectationsArray.forEach(([expectation, desc]) => {
  test(`test ${desc}`, () => {
    expect(expectation).toBeGreaterThanOrEqual(2);
  });
});

J'ai aussi un problème avec la plaisanterie en raison de la nécessité de fournir des messages personnalisés pendant les attentes. Envelopper les attentes sous test semble fonctionner pour les cas où il n'y a pas besoin d'appels asynchrones. Mais comme Jest ne peut pas gérer describe (#2235 ) renvoyant une promesse, nous ne pouvons pas créer de tests avec des appels asynchrones tout en les enveloppant avec test . Et nous ne pouvons pas avoir plusieurs test imbriqués.

Voici un exemple pour illustrer le problème :

async function getArray() {
  return [0,0,0,0,0,0]
}

describe('Custom messages with async', async () => {
  const array = await getArray();
  array.forEach((item) => {
    test(`test${item}`, () => {
      expect(item).toBe(0)
    });
  });
})

Des idées sur la façon de gérer cela?

En regardant le problème dans l'OP ("Ce serait bien s'il y avait un contexte lisible par l'homme qui indiquait immédiatement quelle attente a échoué"), je pense que c'est résolu maintenant. À partir de Jest 22, nous imprimons le contexte de l'assertion défaillante. Le message supplémentaire est-il toujours nécessaire ? Si c'est _is_, il peut s'agir d'un commentaire de code au-dessus ou à côté de l'assertion

image

La description asynchrone est un autre problème (qui ne sera pas aidé par le cadre de code ajouté)

Je pense que je n'utiliserais pas de description asynchrone et utiliserais à la place beforeEach ou beforeAll

@kentcdodds pourriez-vous fournir un exemple sur la façon de gérer cela avec beforeEach ou beforeAll ? Si vous essayez de créer tous les résultats d'appel asynchrones nécessaires dans beforeEach et beforeAll , cela vous forcera à la fin à créer des test imbriqués, ce qui n'est pas autorisé.

Ah, j'ai raté ce que vous faisiez avec cet appel asynchrone. Désolé pour ça 😅 Ouais, tu ne pouvais pas faire beforeEach ou beforeAll pour faire ça.

@SimenB , l'impression du contexte aide déjà beaucoup et résout la plupart des problèmes avec les messages personnalisés. Merci pour cela! Mais ce serait bien d'avoir la possibilité de messages personnalisés explicitement comme argument, car cela aide dans des situations telles que l'utilisation d'attentes dans des boucles.

En regardant le problème dans l'OP ("Ce serait bien s'il y avait un contexte lisible par l'homme qui indiquait immédiatement quelle attente a échoué"), je pense que c'est résolu maintenant.

Oui, cela résout le problème d'origine, tant que vous avez accès à la source d'origine avant la transpilation, ce que la plupart des utilisateurs de Jest auront. IMO, c'est un peu lourd par rapport au fait de permettre aux utilisateurs d'imprimer simplement un message fourni par l'utilisateur avec l'échec, mais assez bon, je suppose.

Je viens de commencer à utiliser Jest et il me manque une fonctionnalité comme celle-ci. Mon cas d'utilisation :
Un test échoue lorsque j'affirme qu'une propriété d'un objet est véridique. Cela m'aiderait à comprendre l'échec plus rapidement si je pouvais enregistrer l'objet en cas d'échec de l'assertion.

Vous pouvez utiliser toHaveProperty pour cela.

test('property', () => {
  expect({foo: 'bar'}).toHaveProperty('baz', 'foobar');
});

image

Si vous voulez juste vérifier qu'il est là, supprimez le deuxième argument. Si vous voulez juste affirmer qu'il a _une_ valeur, vous pouvez utiliser expect.anything() .
toMatchObject est une autre alternative.

Vous pouvez également utiliser assert si vous le souhaitez.

test('property', () => {
  const obj = {foo: 'bar'};
  assert.equal(obj.baz, 'foobar', JSON.stringify(obj));
});

image

Merci pour le conseil. assert.equal(obj.baz, 'foobar', JSON.stringify(obj)); ferait l'affaire dans mon cas particulier.

@SimenB @mpseidel c'est quoi assert ? est-ce une bibliothèque tierce? Je ne trouve rien dans les blagues docs.

@sharikovvladislav assert est un module de base de nœud https://nodejs.org/api/assert.html

@mpseidel oups ! Je ne savais pas. Merci. Ça marche.

J'utilise le fragment de code suivant pour contourner cette limitation du framework (dans TypeScript mais supprimez simplement les annotations de type pour JS)
export const explain = (expectation: () => void, explanation: string) => { try { expectation(); } catch(e) { console.log(explanation) throw e; } }

Salut,
Je suis surpris que personne n'ait encore mentionné les boucles. Le message ne serait pas simplement une chaîne, mais une chaîne dynamique en fonction de l'itération de la boucle.
jest-plugin-context est sympa, merci pour ce travail, mais c'est un peu lourd et le problème initial est toujours d'actualité.
Regarde ce test

describe('MyStuff', () => {
    it('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];
      expectedKeys.forEach((key) => {
        expect(wrapper.find({ id: key }).length).toEqual(1);
      });
    });
  });

Bonne chance pour trouver votre coupable. En ce moment, je dois ajouter une ligne ou tester un objet à la place comme {len:.., key:..} , ce n'est pas propre et pas convivial.
Je pense que ce cas d'utilisation est pertinent, pour les formulaires et la vérification du rendu des éléments par exemple.
La syntaxe pourrait être aussi simple que toEqual(1).context("my message") ou toEqual(1, "my message") (même si bien sûr je sais que la mise en œuvre est toujours plus difficile, et je respecte l'excellent travail que vous avez fait avec Jest).

Peut-être utiliser le même format que chai - c'est-à-dire ajouter le message comme deuxième argument à l'appel expect :

expect(foo, 'this detail').toEqual(2)

J

J'ai déjà utilisé du jasmin, alors je suis venu ici pour découvrir qu'il n'est pas pris en charge.
Cependant, ces choses sont toutes des fonctions. Ne pouvons-nous pas simplement faire quelque chose comme :

describe('MyStuff', () => {
    describe('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];

      expectedKeys.forEach((key) => {
        it(`contains key "${key}"`, () =>
          expect(wrapper.find({ id: key }).length).toEqual(1)
        )
      })
  });
});

2018-04-18-222246_646x390_scrot

@akkerman Belle solution. Étant donné que describe et it sont des globals magiques fournis par jest, je dois admettre qu'ils peuvent sembler obscurs, je n'étais pas sûr que l'écriture "t" dans une boucle puisse fonctionner.

Qu'en est-il de l'enchaînement d'un autre modificateur ?

expect(foo).toEqual(bar).because('reason with %s placeholders')

Ou peut-être une fonction

expect(foo).toEqual(bar).explainedBy((result) => `Lorem ipsum ${result}`)

Je pense qu'un autre modificateur devient rapidement illisible.
J

2018-04-19 13:47 GMT+02:00 λ • Geovani de Souza [email protected] :

Qu'en est-il de l'enchaînement d'un autre modificateur ?

expect(foo).toEqual(bar).because('raison avec %s espaces réservés')

Ou peut-être une fonction

expect(foo).toEqual(bar).explainedBy((result) => Lorem ipsum ${result} )


Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/facebook/jest/issues/1965#issuecomment-382705387 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AAM5PwBCvET1KdEDeDEF7gGo708Naj8oks5tqHlSgaJpZM4Kc6Uu
.

--


Tarjei Huse
Mobile : 920 63 413

La façon dont expect fonctionne est de lancer, donc cela ne fonctionnerait pas de toute façon.

Je pense que expect(something, 'some helpful text on failure').toEqual(somethingElse) ou expect.context(something, 'some helpful text on).toEqual(somethingElse) sont les meilleures alternatives, mais je n'aime pas vraiment l'un ou l'autre

Cela peut-il être rouvert ? Il semble que Jest n'ait toujours pas de bonne solution pour tester comment l'état change sur plusieurs interactions, par exemple :

  • tester comment un conteneur React avec état change en réponse à une série d'événements
  • tester comment une page Web change à travers plusieurs interactions à l'aide de Puppeteer

Ces deux cas nécessitent d'effectuer une série d'actions et d'affirmer comment l'état a changé (ou n'a pas changé) après chaque action, de sorte que des tests multi-assertions sont parfois nécessaires. Il n'est pas toujours possible de résoudre ce genre de problème avec beforeEach .

Je continue à trouver des situations où cela serait vraiment utile. Plus précisément lorsque j'exécute plusieurs interactions et affirmations, comme l'explique @callumlocke .

Si nous proposons une API que les gens ne détestent pas, est-ce quelque chose que vous seriez prêt à poursuivre ? Je pense vraiment que ce serait une fonctionnalité précieuse et très utilisée.

Voici un résumé des solutions proposées :

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

expect(api(), 'api without magic provides no items').toEqual([])
expect(api()).because('api without magic provides no items').toEqual([])
since('api without magic provides no items').expect(api()).toEqual([]))
because('api without magic provides no items').expect(api()).toEqual([]))
jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

Notez qu'un .because() fin n'est pas possible, donc pas inclus en option.

Les quatre options du premier groupe sont prises en charge aujourd'hui. Personnellement, je trouve que la première option (une trame de code avec un commentaire) fonctionne très bien. Et encore mieux que cela, utilisez un matcher personnalisé (option 4).

Je pense que ce que nous devons comprendre pour faire bouger les choses est : qu'y a-t-il de plus attrayant dans les options du deuxième groupe que les options du premier ? Qu'est-ce que le deuxième groupe ajoute qui peut justifier la maintenance de base pour tous les matchers que nous fournissons (entre les matchers asynchrones, les matchers asymétriques, les matchers d'espionnage, les matchers de lancement, les matchers de promesse et les matchers personnalisés) ?

Salut,
Vous avez essentiellement quelques cas d'utilisation :

  • Tests d'assertions multiples (vous devez affirmer plusieurs choses dans un test)
  • Contexte d'assertion généré dynamiquement (vous voulez une variable dans votre message d'échec pour le rendre plus clair, par exemple pour imprimer un champ spécifique de votre objet défaillant parce que vous avez beaucoup de tests)
  • Assertions générées dynamiquement (vous faites une boucle qui génère des assertions)

Les options du premier groupe sont principalement destinées au premier cas d'utilisation. Si vous rencontrez le besoin d'une assertion générée dynamiquement, comme proposé, vous pouvez imbriquer les appels à it et test , afin qu'un test puisse générer de nouveaux tests dans une boucle. Le problème est que vous générez des tests et non des assertions . Imaginez que je veuille affirmer quelque chose sur chaque élément d'un tableau de 1000 éléments, cela va gonfler les résumés de test.

Étant donné que ces cas d'utilisation dynamiques sont encore rares, nous devrions nous en tenir à la solution qui nécessite un minimum de travail pour les responsables. Personnellement, j'aime la solution because/since , car elle semble assez simple. Je suppose que l'implémentation consisterait principalement à envelopper expect dans un try/catch qui imprime le message et le renvoie?
jest.debug semble bizarre, pour moi le débogage imprime un message même si les tests réussissent réellement
L'option "dernier argument" est également bonne mais je ne sais pas si c'est faisable puisque expect peut accepter un nombre variable d'arguments ?

Je suis d'accord avec n'importe quelle option. Je veux juste la fonctionnalité. Je ne suis pas énorme non plus sur l'API jest.debug , mais si c'est la seule qui a du sens, ça me va parce que je veux juste cette fonctionnalité.

@kentcdodds qu'en est-il des quatre options existantes ?

@eric-burel avez-vous vu test.each et describe.each ajoutés dans Jest 23 (disponible en version autonome pour Jest <23) ?

Comme je l'ai dit, je ne me soucie pas vraiment de l'option avec laquelle nous allons. Je veux juste que la fonctionnalité existe. Je suppose que si je devais les trier par ordre de préférence, ce serait:

  1. expect(api(), 'api without magic provides no items').toEqual([])
  2. because('api without magic provides no items').expect(api()).toEqual([]))
  3. since('api without magic provides no items').expect(api()).toEqual([]))
  4. expect(api()).because('api without magic provides no items').toEqual([])
  5. jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

(test|describe).each est génial, mais ne résout pas le problème où vous voulez avoir plusieurs actions/assertions dans un seul test.

La fonctionnalité existe aujourd'hui avec quatre options :

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

Quel est le problème avec ceux-ci? Les _nouvelles_ solutions proposées ne semblent être que légèrement meilleures que ces solutions existantes. Quels avantages apportent-ils par rapport à ce que nous avons qui justifie le coût de maintenance ?

@rickhanlonii Nice Je ne connaissais pas test.each , c'est vraiment une fonctionnalité intéressante, merci de l'avoir signalé. Je pense que cela résout le problème pour mon 3ème cas d'utilisation, un test généré dynamiquement à partir d'un tableau.

Il a donc laissé le deuxième que j'ai listé : avoir un message d'échec généré dynamiquement, ce qui accélérerait le débogage. Je n'ai pas beaucoup de cas d'utilisation pour le moment, peut-être que lorsque vous testez une valeur de champ d'objet, vous souhaitez imprimer l'intégralité de l'objet en cas d'échec. C'est légitime imo, comme tout ce qui facilite l'écriture d'un test, même s'il est marginal ou un peu redondant. Après tout, nous pouvons tous les deux écrire it et test , à moins qu'il y ait une différence que je ne connaisse pas, c'est surtout pour le confort.
C'est marginal mais vraiment quelque chose d'attendu (sans jeu de mots) par les utilisateurs, comme le montre ce fil.

Edit: créer un test dans un autre test avec it ou test et un nom généré dynamiquement pour le test est une solution valide mais je n'aime vraiment pas créer un test quand je veux dire créer une assertion . Je n'aurais jamais deviné que c'était possible si la solution n'avait pas été donnée dans ce fil.

C'est fou. Ajoutez simplement un deuxième paramètre facultatif à expect(). Ceux d'entre nous qui veulent l'utiliser le feront (sélectivement), et ceux qui ne le feront pas ne le feront pas.

Mocha fait ça depuis toujours ... c'est l'une des raisons pour lesquelles j'ai abandonné Jasmine il y a des années (l'autre étant une bien meilleure moquerie de la minuterie.) Si je n'avais pas à rejoindre le mouvement React, je n'utiliserais pas Jest ou tout autre autre dérivé du jasmin.

L'impression d'un message en cas d'erreur est une convention dans de nombreux autres frameworks de test et j'ai été surpris de ne pas le voir dans Jest. J'ai trouvé beaucoup d'exemples utiles dans ce fil (merci pour ceux-ci), mais l'ajout d'un moyen explicite d'imprimer une erreur personnalisée en cas d'échec du test serait un ajout intéressant à la convivialité de Jest. Cela permettrait aux développeurs habitués à d'autres frameworks de test (y compris ceux non-JS) de se lancer plus facilement sur Jest.

@mattphillips pensez -vous qu'il est possible de faire quelque chose de similaire à jest-chain ici pour permettre à une solution d'exister dans userland? Par exemple, deuxième argument de expect

Honnêtement, c'est quelque chose de très standard dans la plupart des frameworks de test JS. Très déçu de ne pas le trouver dans Jest car nous écrivons tous nos tests avec un message d'erreur personnalisé.

@SimenB désolé je n'ai remarqué votre message que ce matin !

Oui, c'est faisable dans userland, je viens de le mettre en place et de le publier en tant que jest-expect-message https://github.com/mattphillips/jest-expect-message

Commentaires bienvenus :sourire:

Génial, merci de l'avoir fait !

@cpojer

La raison pour laquelle je pense que ce n'est pas très utile est que les ingénieurs ne veulent pas perdre de temps à écrire des tests. Tout ce que nous faisons pour leur rendre la tâche plus difficile ne fera que conduire à de pires tests.

Deux choses:

  1. L'ajout d'un deuxième argument facultatif à expect() ne complique guère les choses pour les développeurs.
  2. La dernière chose que les développeurs veulent faire est de perdre du temps à déboguer ce qui a fait échouer le test. Attendu vs reçu est souvent un bon moyen de vérifier si une condition a été remplie, mais souvent pas assez de contexte quant à ce qui l' a fait échouer.

J'ai utilisé Mocha/Chai, ainsi que du ruban adhésif, avant de venir à Jest, et c'est vraiment un facteur décisif. Que devons-nous faire pour obtenir un support de message personnalisé dans expect ?

Nous dire de expect.extend afin de créer un matcher personnalisé ressemble exactement à ce que vous essayiez d'éviter dans votre premier argument : "les ingénieurs ne veulent pas perdre de temps à écrire des tests".

Je trouve facile d'ouvrir le test unitaire et de regarder le numéro de ligne, donc le cas d'utilisation dans l'OP ne me dérange pas.

Le cas d'utilisation qui me dérange est quand j'ai une boucle à l'intérieur d'un test, par exemple pour tester chaque valeur d'un enum, par exemple comme ceci :

it("Should contain at least one word of every wordType", () => {
  for (const wordType of wordTypes) {
    expect(words.find((word) => word.wordType === wordType)).toBeTruthy();
  }
});

Si cela échoue, je ne sais pas quelle valeur wordType a échoué.

Ma solution de contournement consistait à remplacer cela par un message contenant le résultat du test et à m'attendre à ce que le message contienne le résultat du test attendu (c'est-à-dire vrai). En cas d'échec, Jest imprime le message contenant les informations supplémentaires.

    expect(`${wordType} ${!!words.find((word) => word.wordType === wordType)}`).toEqual(`${wordType} ${true}`);

Jest imprime ceci...

expect(received).toEqual(expected)

Difference:

- Expected
+ Received

- 6 true
+ 6 false

... qui me dit que le wordType était 6 quand il a échoué.

Ou plus lisiblement quelque chose comme ...

    if (!words.find((word) => word.wordType === wordType)) {
      expect(`Didn't find a word with wordType '${wordType}'`).toEqual(null);
    }

@cwellsx consultez les tests paramétrés avec test.each cette façon chaque wordType sera un test indépendant avec un titre descriptif (basé sur la valeur)

Ce serait incroyablement utile dans des tests comme celui-ci:

test("compare ArrayBufferCursors", () => {
    const orig: ArrayBufferCursor;
    const test: ArrayBufferCursor;

    expect(test.size).toBe(orig.size);

    while (orig.bytes_left) {
        expect(test.u8()).toBe(orig.u8());
    }
});

À l'heure actuelle, je ne sais que certains octets dans le ArrayBufferCursor sont erronés, mais je ne sais pas lequel. Pouvoir ajouter un index comme contexte rendrait le débogage beaucoup plus facile. Heureusement, certaines personnes ont présenté des solutions de contournement ici, mais elles sont toutes laides et lentes.

@rickhanlonii , je comprends que vous souhaitiez réduire les coûts de maintenance de plaisanterie, mais les options que vous proposez dans votre commentaire augmentent la maintenance des tests unitaires de tous les autres projets. Si je veux expliquer une assertion en utilisant toEqual , ce qui est par ailleurs parfaitement suffisant, me suggérez-vous vraiment d'introduire un matcher personnalisé ?!

Bien qu'il existe des cas avec des tests répétitifs, où it.each sont utiles, devoir ajouter un code inutile pour un simple commentaire sur une assertion rend ce framework de test plus lourd à utiliser.

Cette discussion et l'émergence de jest-expect-message me rappellent le problème de beforeAll à Jasmine...

Je comprends peut-être mal la demande de l'OP ici, mais le problème que j'essayais de résoudre et m'a amené à ce fil de discussion a été résolu en utilisant simplement un simple try / catch et un wrapper autour jest.expect . Tout ce que je voulais faire, c'était enregistrer l'intégralité des objets que j'attendais par rapport à ceux que j'ai reçus + quelques journaux de base expliquant la signification. Bien sûr, cela pourrait être étendu pour faire à peu près tout ce que vous voulez.

La version la plus générique de ceci pourrait ressembler à :

in some test utility file...

const myExpect = (expectFn, errorCallback) => {
  try {
    expectFn();
  } catch (err) {
    errorCallback();
    throw new Error(err);
  }
};

// in the actual test suite...

const context = { hello: "world", results, expected };
myExpect(
    () => expect(results).toEqual(expected),
    () => console.error("[Error] -- context:", JSON.stringify(context))
);

+1
Salut, j'aimerais vraiment cette fonctionnalité aussi. Cela me faciliterait tellement la vie.

Cette page vous a été utile?
0 / 5 - 0 notes