Hey there! So, I've been wrestling with this issue in our Vitest tests where async event handlers weren't causing tests to fail as they should when an assertion inside them failed. I finally cracked it! Let me walk you through what I did – it's actually pretty neat.
First of all
Why should I assert for events
When you're here you might already know but let's round it up anyways.
Assert events in Vitest is a tricky task due to their nature. Events are async and vitest tests are most times run synchronously. Even tho, it is critical to assert events contents in your unit tests since they are a way of communication. Therefore, we need to validate this communication and check whether it works or not.
The Challenge:
Our tests involve async operations, specifically event handlers. When an assertion inside these handlers fails, we expect the test to fail too. But, surprise! It didn't. Instead, Vitest caught unhandled promise rejections, resulting in false positives.
The Solution:
I approached this problem in a few logical steps, ensuring that any async mishaps are caught and dealt with properly.
Step 1: Modify the Event Handler to Return Promises
First up, I modified the event handlers to return promises. This lets us handle the result of the handler more effectively in the test.
javascriptCopy code
const handleObjectCreated = async (event) => {
// ... your logic ...
return new Promise((resolve, reject) => {
try {
// Perform assertions
resolve(true); // Resolve if assertions pass
} catch (error) {
reject(error); // Reject if an assertion fails
}
});
};
Step 2: Update waitForEvent
in eventHelper
Next, I updated the waitForEvent
method in our eventHelper
class. It now expects the handler to return a promise, and it resolves or rejects based on the outcome of this promise.
javascriptCopy code
public waitForEvent(eventName: string, handler: (event: Event) => Promise<any>): Promise<any> {
return new Promise<any>((resolve, reject) => {
const canvasElement = this.getCanvasElement();
const eventListener = async (event: Event) => {
try {
const result = await handler(event);
resolve(result); // Resolve with handler's result
} catch (error) {
reject(error); // Reject on error
} finally {
canvasElement?.removeEventListener(eventName, eventListener, false);
}
};
canvasElement?.addEventListener(eventName, eventListener, false);
});
}
Step 3: Await and Assert in the Test
The final piece of the puzzle was in the test itself. Here, I awaited the result of the waitForEvent
and then asserted that result. If the event handler's promise is rejected (due to a failed assertion), this await will throw, and the subsequent expect
will fail the test.
javascriptCopy code
// Use .resolves to assert that the promise resolves successfully
const createdResult = await helper.waitForEvent("created", handleObjectCreated);
expect(createdResult).toBe(true); // Asserting the result
Wrapping Up:
This setup ensures that if any assertion in our event handler fails, the test fails too, just as it should. It's a neat way to ensure our async code behaves as expected in tests!
So, there you have it. A little bit of promise juggling, some async/await magic, and voilà – reliable async testing in Vitest. If you've got any questions or need more details, feel free to ping me!