$30 off During Our Annual Pro Sale. View Details »

Tips for improving test assertions at Codurance

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.

Marabesi

November 18, 2021
Tweet

More Decks by Marabesi

Other Decks in Programming

Transcript

  1. ASSERTIONS
    codurance.com
    Improving testing

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  5. 1. Background
    Warming up
    Getting started

    View Slide

  6. JOURNEY
    This talk is based on my own

    View Slide

  7. TDD

    View Slide

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

    View Slide

  9. Jest - Framework

    View Slide

  10. Jest - Framework

    View Slide

  11. ANY - Framework

    View Slide

  12. ANY - Framework

    View Slide

  13. toEqual
    usually is the first assertion used❗

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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) ✅

    View Slide

  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) ✅

    View Slide

  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) ✅

    View Slide

  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) ✅

    View Slide

  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) ✅

    View Slide

  27. When Test Driven Development Goes Wrong
    Dave Farley - by Continuous Delivery

    View Slide

  28. A more expressive way? 🤔
    IS THERE

    View Slide

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

    View Slide

  30. Primitive assertions 🧱
    Crafting code

    View Slide

  31. Modifiers 🪣
    Crafting code

    View Slide

  32. Callbacks 📱
    Crafting code

    View Slide

  33. Timers ⏰
    Crafting code

    View Slide

  34. Async 🏹
    Crafting code

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  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
    🧱

    View Slide

  58. Modifiers 🪣
    Not
    Crafting code

    View Slide

  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
    🪣

    View Slide

  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
    🪣

    View Slide

  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
    🪣

    View Slide

  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
    🪣

    View Slide

  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
    🪣

    View Slide

  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
    🪣

    View Slide

  65. Not in the document example
    Not

    View Slide

  66. Callbacks 📱
    Functions
    Crafting code

    View Slide

  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();

    View Slide

  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();

    View Slide

  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();

    View Slide

  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();

    View Slide

  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();

    View Slide

  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();

    View Slide

  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();

    View Slide

  74. Timers ⏰
    setTimeout, setInterval, clearTimeout,
    clearInterval
    Crafting code

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  79. describe('using fake timers', () => {
    beforeEach(() => {
    jest.useFakeTimers()
    })
    afterEach(() => {
    jest.useRealTimers()
    })
    })
    Timers

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  86. Async 🏹
    Promises
    Crafting code

    View Slide

  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
    🏹

    View Slide

  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
    🏹

    View Slide

  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
    🏹

    View Slide

  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
    🏹

    View Slide

  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
    🏹

    View Slide

  92. 6. Wrapping up
    We are almost done!
    Crafting code

    View Slide

  93. ● toEqual is usually used more than any assertion
    ● Assertion smells
    ● Using different assertions can improve understanding
    ● 🧱, 🪣,📱, ⏰, 🏹
    What we covered

    View Slide

  94. What we covered
    HAPPY TESTING 🤪

    View Slide

  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

    View Slide