Mon "Wow, je ne savais pas ça!" moments avec plaisanterie

Bonjour à tous! Le cours de développeur JavaScript démarre ce jeudi. À cet égard, nous avons décidé de partager la traduction d'un autre matériel intéressant. Bonne lecture.



Jest a toujours été mon outil de test unitaire indispensable. Il est tellement fiable que je commence à penser que je l'ai toujours sous-utilisé. Bien que les tests aient fonctionné, au fil du temps, je les ai refaçonnés ici et là, car je ne savais pas que Jest pouvait le faire. A chaque fois c'est un nouveau code quand je vérifie la documentation Jest.

Donc, je vais partager quelques-unes de mes astuces Jest préférées que certains d'entre vous connaissent peut-être déjà parce que vous lisez la documentation et que vous ne m'aimez pas (honte), mais j'espère que cela aidera ceux qui viennent de la parcourir rapidement !

Soit dit en passant, j'utilise Jest v24.8.0 comme matériel de référence, alors faites attention, certaines choses peuvent ne pas fonctionner avec la version de Jest que vous utilisez actuellement. De plus, les exemples ne représentent pas le code de test réel, ce n'est qu'une démonstration.

# 1 .toBe vs .toEqual


Au début, toutes ces déclarations me semblaient normales:

expect('foo').toEqual('foo') expect(1).toEqual(1) expect(['foo']).toEqual(['foo']) 

Basé sur l'utilisation de chai pour les déclarations d'égalité (to.equal), cela est tout à fait naturel. En fait, Jest ne se plaindra pas, et ces déclarations passent comme d'habitude.

Cependant, Jest a .toBe et .toEqual. Le premier est utilisé pour affirmer l'égalité à l'aide d' Object.is , et le second est de fournir une comparaison approfondie des objets et des tableaux. .toEqual a un recours à Object.is s'il s'avère que vous n'avez pas besoin d'une comparaison approfondie, comme affirmer des égalités pour des valeurs primitives, ce qui explique pourquoi l'exemple précédent s'est très bien passé.

 expect('foo').toBe('foo') expect(1).toBe(1) expect(['foo']).toEqual(['foo']) 

De cette façon, vous pouvez ignorer tous les if-else dans .toEqual utilisant .toBe si vous savez déjà quelles valeurs vous testez.
Une erreur courante est que vous utiliserez .toBe pour affirmer l'égalité des valeurs primitives.

 expect(['foo']).toBe(['foo']) 

Si vous regardez le code source lorsque .toBe se bloque, il essaiera de déterminer si vous avez vraiment fait cette erreur en appelant la fonction utilisée par .toEqual. Cela peut être un goulot d'étranglement lors de l'optimisation de votre test.

Si vous êtes sûr d'utiliser des valeurs primitives, votre code peut être réorganisé comme tel à des fins d'optimisation:

 expect(Object.is('foo', 'foo')).toBe(true) 

Plus de détails dans la documentation .

# 2 Des comparaisons plus adaptées


Techniquement, vous pouvez utiliser .toBe pour valider toutes les valeurs. Avec Jest, vous pouvez utiliser spécifiquement certains outils de comparaison pour rendre votre test plus lisible (et dans certains cas plus court).

 // expect([1,2,3].length).toBe(3) // expect([1,2,3]).toHaveLength(3) const canBeUndefined = foo() // expect(typeof canBeUndefined !== 'undefined').toBe(true) // expect(typeof canBeUndefined).not.toBe('undefined') // expect(canBeUndefined).not.toBe(undefined) // expect(canBeUndefined).toBeDefined() class Foo { constructor(param) { this.param = param } // expect(new Foo('bar') instanceof Foo).toBe(true) // expect(new Foo('bar')).toBeInstanceOf(Foo) 

Ce ne sont que quelques-uns de ceux que j'ai sélectionnés dans la longue liste de compilateurs Jest dans la documentation, vous pouvez jeter un œil au reste vous-même.

# 3 Test d'instantanés sur des éléments sans interface utilisateur


Vous avez peut-être entendu parler du test d'instantanés dans Jest , où il permet de suivre les modifications apportées aux éléments de votre interface utilisateur. Mais les tests avec des instantanés ne se limitent pas à cela.

Considérez cet exemple:

 const allEmployees = getEmployees() const happyEmployees = giveIncrementByPosition(allEmployees) expect(happyEmployees[0].nextMonthPaycheck).toBe(1000) expect(happyEmployees[1].nextMonthPaycheck).toBe(5000) expect(happyEmployees[2].nextMonthPaycheck).toBe(4000) // ...etc 

Ce serait fatigant si vous réclamiez de plus en plus d'employés. De plus, s'il s'avère que plus de demandes sont nécessaires pour chaque employé, multipliez le nombre de nouvelles demandes par le nombre d'employés et vous aurez une idée.
Avec le test d'instantané, tout cela peut être fait simplement comme ceci:

 const allEmployees = getEmployees() const happyEmployees = giveIncrementByPosition(allEmployees) expect(happyEmployees).toMatchSnapshot() 

Chaque fois que des régressions se produisent, vous saurez exactement quel arbre dans le nœud ne correspond pas à l'image.

Mais la commodité a un prix: cette méthode est plus sujette aux erreurs. Il est possible que vous ne sachiez pas que l'image est en fait incorrecte, et à la fin vous la prendrez quand même. Vérifiez donc votre instantané comme s'il s'agissait de votre propre code d'approbation (car il l'est).

Bien sûr, les tests ne se limitent pas aux instantanés. Lisez la documentation complète.

# 4 decrire.chaque et test.chaque


Avez-vous déjà passé un test quelque peu similaire à celui-ci?

 describe('When I am a supervisor', () => { test('I should have a supervisor badge', () => { const employee = new Employee({ level: 'supervisor' }) expect(employee.badges).toContain('badge-supervisor') }) test('I should have a supervisor level', () => { const employee = new Employee({ level: 'supervisor' }) expect(employee.level).toBe('supervisor') }) }) describe('When I am a manager', () => { test('I should have a manager badge', () => { const employee = new Employee({ level: 'manager' }) expect(employee.badges).toContain('badge-manager') }) test('I should have a manager level', () => { const employee = new Employee({ level: 'manager' }) expect(employee.level).toBe('manager') }) }) 

C'est monotone et routinier, non? Imaginez faire cela avec beaucoup de cas.
Avec description.each et test.each vous pouvez compresser le code comme suit:

 const levels = [['manager'], ['supervisor']] const privileges = [['badges', 'toContain', 'badge-'], ['level', 'toBe', '']] describe.each(levels)('When I am a %s', (level) => { test.each(privileges)(`I should have a ${level} %s`, (kind, assert, prefix) => { const employee = new Employee({ level }) expect(employee[kind])[assert](`${prefix}${level}`) }) }) 

Cependant, je n'ai pas encore utilisé cela dans mon propre test, car je préfère que mon test soit détaillé, mais je pensais juste que ce serait une astuce intéressante.

Voir la documentation pour plus de détails sur les arguments (spoiler: la syntaxe du tableau est vraiment cool).

# 5 Imitation unique des fonctions globales


À un moment donné, vous devrez tester quelque chose qui dépend des fonctions globales dans un cas de test particulier. Par exemple, une fonction qui reçoit des informations sur la date actuelle à l'aide de l'objet javascript Date ou une bibliothèque qui en dépend. La difficulté est qu'en ce qui concerne la date actuelle, vous ne pouvez jamais obtenir la bonne déclaration.

 function foo () { return Date.now() } expect(foo()).toBe(Date.now()) // This would throw occasionally: // expect(received).toBe(expected) // Object.is equality // // Expected: 1558881400838 // Received: 1558881400837 


En fin de compte, vous devrez redéfinir l'objet Date global afin qu'il soit cohérent et gérable:

 function foo () { return Date.now() } Date.now = () => 1234567890123 expect(foo()).toBe(1234567890123) // 

Cependant, cela est considéré comme une mauvaise pratique, car la redéfinition est maintenue entre les tests. Vous ne le remarquerez pas s'il n'y a pas d'autre test basé sur Date.now, mais il fuira aussi.

 test('First test', () => { function foo () { return Date.now() Date.now = () => 1234567890123 expect(foo()).toBe(1234567890123) // }) test('Second test', () => { function foo () { return Date.now() expect(foo()).not.toBe(1234567890123) // ??? }) 

J'avais l'habitude de le "craquer" pour qu'il ne fuit pas:

 test('First test', () => { function foo () { return Date.now() const oriDateNow = Date.now Date.now = () => 1234567890123 expect(foo()).toBe(1234567890123) // Date.now = oriDateNow }) test('Second test', () => { function foo () { return Date.now() expect(foo()).not.toBe(1234567890123) // as expected }) 

Cependant, il existe une manière bien meilleure et moins piratée de procéder:

 test('First test', () => { function foo () { return Date.now() jest.spyOn(Date, 'now').mockImplementationOnce(() => 1234567890123) expect(foo()).toBe(1234567890123) // }) test('Second test', () => { function foo () { return Date.now() expect(foo()).not.toBe(1234567890123) // as expected }) 

Ainsi, jest.spyOn suit l'objet global Date. Il imite l'implémentation de la fonction now pour un seul appel. Cela laissera à son tour Date.now intact pour le reste des tests.

Il y a certainement plus d'informations sur les talons à Jest. Voir la documentation complète pour plus de détails.

Cet article est assez long, donc je pense que c'est tout pour le moment. Cela n'affecte qu'une petite partie des capacités de Jest, et je souligne juste mes favoris. Si vous avez d'autres faits intéressants, faites-le moi savoir.

De plus, si vous avez souvent utilisé Jest, consultez Majestic , qui est une interface graphique sans interface graphique pour Jest, une très bonne alternative à la sortie ennuyeuse du terminal. Je ne sais pas si l'auteur est en dev.to, mais néanmoins du respect pour cette personne.

Comme toujours, merci de votre attention!

C’est tout. Rendez-vous sur le parcours.

Source: https://habr.com/ru/post/fr457616/


All Articles