Unit testing JS generator functions in Jest
// Written by Arjun Komath
// Sat, Jan 27 2024
Testing generator functions was a bit of a puzzle for me. I searched high and low for resources to guide me through it, but came up empty-handed. So, I took matters into my own hands and decided to write something myself.
Throughout this article, we’ll be crafting multiple unit tests for the following code:
export function* world() {
return "world";
}
export function* hello() {
const result = ["hello"];
result.push(yield world());
return result.join(" ");
}
Testing Hello World
Consider this straightforward example for testing a hello()
function that yields results from world()
. What’s intriguing is that our test function is also a generator function. This approach allows us to sidestep the need to create an iterator for iterating through the values generated by hello().
test("hello world", function* () {
const message = yield hello();
expect(message).toEqual("hello world");
});
In a more realistic setup, you’ll find yourself testing a lot more scenarios, such as handling failures, verifying the number of calls made, and ensuring the returned data meets expectations.
Mocking nested yield
In this example, we’re going to simulate a nested yield to mimic a real-world scenario where we’re interacting with an external system. This approach enables us to test various scenarios where different values are returned as side-effects.
const { hello } = require("../dist/hello");
const { world } = require("../dist/world");
jest.mock("../dist/world");
test("hello world", function* () {
world.mockReturnValue(function* () {
return "something else";
});
const message = yield hello();
expect(world).toHaveBeenCalledTimes(1);
expect(message).toEqual("hello something else");
});
Testing failures
This is a bit peculiar. I encountered an issue where I couldn’t get Jest’s error testers to cooperate. Instead, I opted for wrapping the calls within a try-catch block and then anticipating the error. I’m not entirely convinced this is the optimal approach. Let me know if you have any ideas for improvement.
test("hello world", function* () {
world.mockReturnValue(function* () {
throw "Oops!";
});
try {
yield hello();
} catch (e) {
expect(e).toBe("Oops!");
}
});