Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tips for improving test assertions at Codurance

Ed39ca0d44a6e6cdefc76ac548de5f41?s=47 Marabesi
November 18, 2021

Tips for improving test assertions at Codurance

Testing with jest is an activity that developers do to keep the application maintainable and time proof. Therefore, learning a testing framework can be a consuming task, often it has many features to master. The assertion API usually is one of the most important ones, as this is the one that the developer uses the most during the TDD flow.

The gist of the assertion API is to compare values, as such the equals matcher is the most used. Being one of the most used can also point to a lack of knowledge in the different assertions that the testing framework offers.

This talk aims to cover different assertions, to avoid using always toEqual and make the test case more expressive. For each example, I try to first depict how it would be with toEqual, then I show another way using a different assertion.

Ed39ca0d44a6e6cdefc76ac548de5f41?s=128

Marabesi

November 18, 2021
Tweet

Transcript

  1. ASSERTIONS codurance.com Improving testing

  2. Matheus Marabesi Hello there, you can call me Marabesi, But

    my name is Matheus Marabesi, I work at Codurance as a Software craftsperson. I enjoy talking about anything related to: testing, patterns and gamification. You can find me at @MatheusMarabesi or https://marabesi.com Codurance Crafting Code
  3. 1. Intro - Background 2. Assertion smells 3. Assertion API

    4. Exploring assertions 5. Wrapping up Crafting code Agenda
  4. 1. Intro - Background 2. Assertion smells 3. Assertion API

    - Jest 4. Exploring assertions 5. Wrapping up Crafting code Agenda
  5. 1. Background Warming up Getting started

  6. JOURNEY This talk is based on my own

  7. TDD

  8. https://app.livestorm.co/codurance/testing-anti-patterns-workshop

  9. Jest - Framework

  10. Jest - Framework

  11. ANY - Framework

  12. ANY - Framework

  13. toEqual usually is the first assertion used❗

  14. 🤔 And, it's something like . . .

  15. Assert test('assert some value', () => { const myVar =

    'some val' expect(myVar).toEqual('some val') })
  16. Assert test('assert some value', () => { const myVar =

    'some val' expect(myVar).toEqual('some val') ✅ })
  17. Assert test('light is on', () => { const off =

    false expect(off === false).toEqual(true) })
  18. Assert test('light is on', () => { const off =

    false expect(off === false).toEqual(true) ✅ })
  19. Assert test('it has two users', () => { const users

    = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).toEqual(2) })
  20. Assert test('it has two users', () => { const users

    = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).toEqual(2) ✅ })
  21. 2. Assertion smells Smells are something that developers relate to,

    has some insight that it's not the best approach and still can't figure out what. Crafting code
  22. Assertion smells const myVar = 'some val' expect(myVar).equals('some val') ✅

    const off = false expect(off === false).equals(true) ✅ const users = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).equals(2) ✅
  23. Assertion smells const myVar = 'some val' expect(myVar).equals('some val') ✅

    const off = false expect(off === false).equals(true) ✅ const users = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).equals(2) ✅
  24. Assertion smells const myVar = 'some val' expect(myVar).equals('some val') ✅

    const off = false expect(off === false).equals(true) ✅ const users = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).equals(2) ✅
  25. Assertion smells const myVar = 'some val' expect(myVar).equals('some val') ✅

    const off = false expect(off === false).equals(true) ✅ const users = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).equals(2) ✅
  26. Assertion smells const myVar = 'some val' expect(myVar).equals('some val') ✅

    const off = false expect(off === false).equals(true) ✅ const users = [ { name: 'jhon'}, { name: 'maria' } ] expect(users.length).equals(2) ✅
  27. When Test Driven Development Goes Wrong Dave Farley - by

    Continuous Delivery
  28. A more expressive way? 🤔 IS THERE

  29. 3. Assertion API 5⃣ Meeting the jest assertion API Crafting

    code
  30. Primitive assertions 🧱 Crafting code

  31. Modifiers 🪣 Crafting code

  32. Callbacks 📱 Crafting code

  33. Timers ⏰ Crafting code

  34. Async 🏹 Crafting code

  35. Primitive assertions 🧱 Modifiers 🪣 Callbacks 📱 Timers ⏰ Async

    🏹 Crafting code
  36. 4. Exploring assertions Primitive, modifier, callback, timer and async. Crafting

    code
  37. Primitive assertions 🧱 Numbers, Booleans and Arrays Crafting code

  38. test('it returns a number', () => { const isNumber =

    number => number expect(typeof isNumber(2)).toEqual('number') }) test('it returns a number', () => { const isNumber = number => number expect(isNumber(2)).toEqual(expect.any(Number)) }) Numbers 🧱
  39. test('it returns a number', () => { const isNumber =

    number => number expect(typeof isNumber(2)).toEqual('number') }) test('it returns a number', () => { const isNumber = number => number expect(isNumber(2)).toEqual(expect.any(Number)) }) Numbers 🧱
  40. test('it returns a number', () => { const isNumber =

    number => number expect(typeof isNumber(2)).toEqual('number') }) test('it returns a number', () => { const isNumber = number => number expect(isNumber(2)).toEqual(expect.any(Number)) }) Numbers 🧱
  41. test('it returns a number', () => { const isNumber =

    number => number expect(typeof isNumber(2)).toEqual('number') }) test('it returns a number', () => { const isNumber = number => number expect(isNumber(2)).toEqual(expect.any(Number)) }) Numbers 🧱
  42. test('expected is greater than the desired', () => { const

    expected = 10 const actual = 3 expect(expected > actual).toEqual(true) }) test('expected is greater than the desired', () => { const expected = 10 const actual = 3 expect(expected).toBeGreaterThan(actual) }) Booleans 🧱
  43. test('expected is greater than the desired', () => { const

    expected = 10 const actual = 3 expect(expected > actual).toEqual(true) }) test('expected is greater than the desired', () => { const expected = 10 const actual = 3 expect(expected).toBeGreaterThan(actual) }) Booleans 🧱
  44. test('expected is greater than the desired', () => { const

    expected = 10 const actual = 3 expect(expected > actual).toEqual(true) }) test('expected is greater than the desired', () => { const expected = 10 const actual = 3 expect(expected).toBeGreaterThan(actual) }) Booleans 🧱
  45. test('expected is greater than the desired', () => { const

    expected = 10 const actual = 3 expect(expected > actual).toEqual(true) }) test('expected is greater than the desired', () => { const expected = 10 const actual = 3 expect(expected).toBeGreaterThan(actual) }) Booleans 🧱
  46. test('list three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('list three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Arrays 🧱
  47. test('list three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('list three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Arrays 🧱
  48. test('list three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('list three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Arrays 🧱
  49. test('list three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('list three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual( expect.arrayContaining(actualFruits) ) }) Arrays 🧱
  50. test('list three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('list three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual(expect.arrayContaining(actualFruits)) }) Arrays 🧱
  51. test('list three fruits', () => { const expectedFruits = ['banana',

    'mango', 'watermelon'] expect(expectedFruits[0]).toEqual('banana') expect(expectedFruits[1]).toEqual('mango') expect(expectedFruits[2]).toEqual('watermelon') }) test('list three fruits', () => { const expectedFruits = ['banana', 'mango', 'watermelon'] const actualFruits = ['banana', 'mango', 'watermelon'] expect(expectedFruits).toEqual(expect.arrayContaining(actualFruits)) }) Arrays 🧱
  52. test('list with three numbers', () => { const myList =

    [1, 2, 3] expect(myList.length).toEqual(3) }) test('list with three numbers', () => { const myList = [1, 2, 3] expect(myList).toHaveLength(3) }) Arrays 🧱
  53. test('list with three numbers', () => { const myList =

    [1, 2, 3] expect(myList.length).toEqual(3) }) test('list with three numbers', () => { const myList = [1, 2, 3] expect(myList).toHaveLength(3) }) Arrays 🧱
  54. test('list with three numbers', () => { const myList =

    [1, 2, 3] expect(myList.length).toEqual(3) }) test('list with three numbers', () => { const myList = [1, 2, 3] expect(myList).toHaveLength(3) }) Arrays 🧱
  55. test('list with three numbers', () => { const myList =

    [1, 2, 3] expect(myList.length).toEqual(3) }) test('list with three numbers', () => { const myList = [1, 2, 3] expect(myList).toHaveLength(3) }) Arrays 🧱
  56. test('list with three numbers', () => { const myList =

    [1, 2, 3] expect(myList.length).toEqual(3) }) test('list with three numbers', () => { const myList = [1, 2, 3] expect(myList).toHaveLength(3) }) Arrays 🧱
  57. test('list with three numbers', () => { const myList =

    [1, 2, 3] expect(myList.length).toEqual(3) }) test('list with three numbers', () => { const myList = [1, 2, 3] expect(myList).toHaveLength(3) }) Arrays 🧱
  58. Modifiers 🪣 Not Crafting code

  59. test('light is on', () => { const isOff = false

    expect(!isOff).toBe(true) }) test('light is on', () => { const isOff = false expect(isOff).not.toBe(true) }) Not 🪣
  60. test('light is on', () => { const isOff = false

    expect(!isOff).toBe(true) }) test('light is on', () => { const isOff = false expect(isOff).not.toBe(true) }) Not 🪣
  61. test('light is on', () => { const isOff = false

    expect(!isOff).toBe(true) }) test('light is on', () => { const isOff = false expect(isOff).not.toBe(true) }) Not 🪣
  62. test('light is on', () => { const isOff = false

    expect(!isOff).toBe(true) }) test('light is on', () => { const isOff = false expect(isOff).not.toBe(true) }) Not 🪣
  63. test('light is on', () => { const isOff = false

    expect(!isOff).toBe(true) }) test('light is on', () => { const isOff = false expect(isOff).not.toBe(true) }) Not 🪣
  64. test('light is on', () => { const isOff = false

    expect(!isOff).toBe(true) }) test('light is on', () => { const isOff = false expect(isOff).not.toBe(true) }) Not 🪣
  65. Not in the document example Not

  66. Callbacks 📱 Functions Crafting code

  67. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  68. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  69. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  70. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  71. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  72. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  73. test('callback has been invoked', done => { callAsyncFunc(() => {

    expect(true).toEqual(true) done() }) }) test('callback has been invoked', () => { const result = jest.fn() callAsyncFunc(result) expect(result).toHaveBeenCalled() }) Callbacks 📱 const callAsyncFunc = cb => cb();
  74. Timers ⏰ setTimeout, setInterval, clearTimeout, clearInterval Crafting code

  75. Asynchronous Behavior Eradicating Non-Determinism in Tests Martin Fowler, 2011

  76. function callAsyncFunction() { return new Promise((resolve) => { setTimeout(() =>

    { resolve(true); }, 1000); }); } test('assert after some time', done => { callAsyncFunction(). then((value) => { expect(value).toBe(true) done() }) }) Timers ⏰
  77. function callAsyncFunction() { return new Promise((resolve) => { setTimeout(() =>

    { resolve(true); }, 1000); }); } test('assert after some time', done => { callAsyncFunc(). then((value) => { expect(value).toBe(true) done() }) }) Timers ⏰
  78. function callAsyncFunction() { return new Promise((resolve) => { setTimeout(() =>

    { resolve(true); }, 1000); }); } test('assert after some time', done => { callAsyncFunction(). then((value) => { expect(value).toBe(true) done() }) }) Timers ⏰
  79. describe('using fake timers', () => { beforeEach(() => { jest.useFakeTimers()

    }) afterEach(() => { jest.useRealTimers() }) }) Timers ⏰
  80. test("should handle next scene", async () => { const value

    = callAsyncFunction(); jest.advanceTimersByTime(2000); expect(await value).toBe(true); }); Timers ⏰
  81. test("should handle next scene", async () => { const value

    = callAsyncFunction(); jest.advanceTimersByTime(2000); expect(await value).toBe(true); }); Timers ⏰
  82. test("should handle next scene", async () => { const value

    = callAsyncFunction(); jest.advanceTimersByTime(2000); expect(await value).toBe(true); }); Timers ⏰
  83. test("should handle next scene", async () => { const value

    = callAsyncFunction(); jest.advanceTimersByTime(2000); expect(await value).toBe(true); }); Timers ⏰
  84. test("should handle next scene", async () => { const value

    = callAsyncFunction(); jest.advanceTimersByTime(2000); expect(await value).toBe(true); }); Timers ⏰
  85. test("should handle next scene", async () => { const value

    = callAsyncFunction(); jest.advanceTimersByTime(2000); expect(await value).toBe(true); }); Timers ⏰
  86. Async 🏹 Promises Crafting code

  87. test('my async test with then', done => { callAsyncFunction(). then((value)

    => { expect(value).toBe(true) done() }) }) test('my async test', async () => { await expect(callAsyncFunction()).resolves.toEqual(true) }) test('my async test rejects', async () => { await expect(rejectsAsyncFunction()).rejects.toEqual(false) }) Promises 🏹
  88. test('my async test with then', done => { callAsyncFunction(). then((value)

    => { expect(value).toBe(true) done() }) }) test('my async test', async () => { await expect(callAsyncFunction()).resolves.toEqual(true) }) test('my async test rejects', async () => { await expect(rejectsAsyncFunction()).rejects.toEqual(false) }) Promises 🏹
  89. test('my async test with then', done => { callAsyncFunction(). then((value)

    => { expect(value).toBe(true) done() }) }) test('my async test', async () => { await expect(callAsyncFunction()).resolves.toEqual(true) }) test('my async test rejects', async () => { await expect(rejectsAsyncFunction()).rejects.toEqual(false) }) Promises 🏹
  90. test('my async test with then', done => { callAsyncFunction(). then((value)

    => { expect(value).toBe(true) done() }) }) test('my async test', async () => { await expect(callAsyncFunction()).resolves.toEqual(true) }) test('my async test rejects', async () => { await expect(rejectsAsyncFunction()).rejects.toEqual(false) }) Promises 🏹
  91. test('my async test with then', done => { callAsyncFunction(). then((value)

    => { expect(value).toBe(true) done() }) }) test('my async test', async () => { await expect(callAsyncFunction()).resolves.toEqual(true) }) test('my async test rejects', async () => { await expect(rejectsAsyncFunction()).rejects.toEqual(false) }) Promises 🏹
  92. 6. Wrapping up We are almost done! Crafting code

  93. • toEqual is usually used more than any assertion •

    Assertion smells • Using different assertions can improve understanding • 🧱, 🪣,📱, ⏰, 🏹 What we covered
  94. What we covered HAPPY TESTING 🤪

  95. Matheus Marabesi Hello there, you can call me Marabesi, But

    my name is Matheus Marabesi, I work at Codurance as a Software craftsperson. I enjoy talking about anything related to: testing, patterns and gamification. You can find me at @MatheusMarabesi or https://marabesi.com Codurance Crafting Code