Improving Your Jest Tests with Custom Jest Matchers and jest-matcher-util.

Improving Your Jest Tests with Custom Jest Matchers and jest-matcher-util.

Tags
react
jest
Published
December 19, 2024
Author

Introduction

Writing clear readable tests are a great way to make your code base more reliable and easier to use for yourself and others. I have been using the Jest testing library to write tests for my React code base and I have started to use Jest matchers to make my tests more concise and make the error messages more descriptive.
 
I came across this idea along with the code examples from the excellent book called React Test Driven Development by Daniel Irvine.

Step 1: Clear tests with Custom Jest Matchers

In this example we are using the standard jest library to access the DOM and check for the the text “Ashley”.
it("selects the first appointment by default", () => { render( <AppointmentsDayView appointments={twoAppointments} /> ); expect(document.body.textContent).toContain( "Ashley" ); });
By extracting the Jest matcher into a Custom Jest Matcher we can make the text easier to read.
This is the custom Jest matcher:
import { matcherHint, printExpected, printReceived, } from "jest-matcher-utils"; export const toContainText = ( received, expectedText ) => { const pass = received.textContent.includes(expectedText); return { pass }; };
Now the code reads like this:
it("selects the first appointment by default", () => { render( <AppointmentsDayView appointments={twoAppointments} /> ); expect(document.body).toContainText("Ashley"); });
This test is slightly easier to read however the main benefit is that we can make use of the jest-matcher-util library to significantly improve the error message and Jest formatting that out test will produce.
 

Step 2: Extend the Jest expect class

Inorder for the new method to work with the expect class you have to extend the Jest class.
expect.extend({ toContainText, });

Step 2: Better Error Messages using jest-matcher-util

When you write a test its common to get a cryptic fail message, for example:
expect(array.some(obj => obj.id === 123)).toBe(true); result: “Expected true, received false.”
When you write. a custom Jest matcher you can anticipate the types of error that will be produced and give the reader some context around how the test has failed:
expect(array).toContainObjectWithId(123); Received array: [ { id: 456, name: "Alice" }, { id: 789, name: "Bob" } ] Expected an object with id: 123, but none found.
 
To do this I am using the jest-matcher-utils library with in a Jest Custom matcher to produce clear, consistent and informative error messages.
import { matcherHint, printExpected, printReceived, } from "jest-matcher-utils"; export const toContainText = ( received, expectedText ) => { const pass = received.textContent.includes(expectedText); const sourceHint = () => matcherHint( "toContainText", "element", printExpected(expectedText), { isNot: pass } ); const actualTextHint = () => "Actual text: " + printReceived(received.textContent); const message = () => [sourceHint(), actualTextHint()].join("\n\n"); return { pass, message }; };
 
MatcherHint is a method that defines my new error message in a way that passes more relevant information to eh developer and also make the message well formatted with in the Jest library.
 
This is an example of how that works:
FAIL test/matchers/toContainText.test.js ● toContainText matcher › return a message that contains the actual text ewith out the jest helper expect(received).toBe(expected) // Object.is equality Expected: true Received: false 64 | const result = domElement.textContent.includes("text to findx") 65 | > 66 | expect(result).toBe(true) | ^ 67 | }) 68 | }) at Object.toBe (test/matchers/toContainText.test.js:66:20)
 
FAIL test/matchers/toContainText.test.js ● toContainText matcher › return a message that contains the actual text expect(received).toContain(expected) // indexOf Expected substring: "Actual Text: \"text to findx\"" Received string: "expect(element).not.toContainText(\"text to find\")· Actual Text: \"text to find\"" 56 | ); 57 | > 58 | expect(stripTerminalColor(result.message())).toContain( | ^ 59 | `Actual Text: "text to findx"` 60 | ) 61 | }) at Object.toContain (test/matchers/toContainText.test.js:58:50)
 

Conclusion

Code testing is both an art and a science. The use of custom Jest matchers to extend the standard library along with these formatting tool are something that have made my Jest test easier to eat and more informative. I have avoided the problem of seeing that there is an error but having to do 15 minutes of console.logging to find out what that error actually is.