Hooks

One day, our Peter the Project Manager comes to us with a great idea to improve the app. He suggests that we wish our users a happy day as a part of the greeting message (a bit of kindness goes a long way). That sounds easy enough, and so we change the greet() function to reflect that suggestion:
export function greet(name: string) {
	const weekday = new Date().toLocaleDateString('en-US', { weekday: 'long' })

	return `Hello, ${name}! Happy, ${weekday}.`
}
Since the intention behind the code has changed (now it also includes the day of the week), we should adjust the relevant tests to capture that:
test('returns a greeting message for the given name', () => {
	expect(greet('John')).toBe('Hello, John! Happy, Monday.')
})
The issue with this test is that it will only pass on Mondays! That won't do. We need a deterministic test, no matter where or when we run it. To fix this, let's first understand why this happens.
Our greet() function constructs a Date to extract a weekday from it. This is a perfectly valid logic but it's also a side effect. The values that Date returns are not predictable—they change based on, well, the current date! We need to account for that change in our test by, somehow, freezing the current date and making it return the same value on every test run.
The usage of Date in this case is just an example of a side effect your code may introduce. Whatever it is, the handling of such side effects is often the same.
To handle side effects such as this one, tests often have the concept of "hooks". Hooks are functions that allow you to execute arbitrary code before, during, and after the test run. The purpose of hooks is to help you prepare the environment that runs your tested code.
🐨 In this exercise, you'd have to implement two new global functions (hooks) in setup.js:
  • beforeAll(), which accepts a callback argument and runs it before all tests;
  • afterAll(), which accepts a callback argument and runs it after the tests are done.
You can treat the beforeExit event of the Node.js process as the indicator that all tests are done.
Once you do, add a beforeAll() hook to the greet.test.js to patch the globalThis.Date constructor and return a fixed date from it. Make sure to undo the patch after the tests to keep the test environment clean. And, of course, make sure the tests are passing.
Feel free to use the following snippet to mock the Date constructor:
const OriginalDate = globalThis.Date

beforeAll(() => {
	globalThis.Date = new Proxy(globalThis.Date, {
		construct: () => new OriginalDate('2024-01-01'),
	})
})

afterAll(() => {
	globalThis.Date = OriginalDate
})
📜 The Proxy API in JavaScript allows you to spy on any object. In our case, we will spy on the constructor calls to the Date global object and make it return the same date consistently.

The Golden Rule of Assertions

This problem illustrates the importance of the testing setup for the quality of your tests. Our code does what we intended but the tests still fail. When that happens, you know you've got a bad test. Turns out, such tests don't pass The Golden Rule of Assertions:
A test must fail if, and only if, the intention behind the system is not met.
🦉 It's a fantastic rule to keep in mind when writing tests. Learn more about The Golden Rule of Assertion and how it can help you write better tests every single time.

Please set the playground first

Loading "Hooks"
Loading "Hooks"
Login to get access to the exclusive discord channel.