I’ve been using this pattern for my JS/TS code for a while now and I think it works well for creating reusable test fixture logic:
One of the hardest things about testing is establishing the context. You need test data that ideally reflects reality as closely as possible. End-to-end tests that activate the entire system are great because they work with real data but they are slow to execute and perhaps more importantly, tedious to set up. Integration tests and unit tests are much better suited for testing permutations in the logic — all the non-happy paths, all the edge cases.
I find that the code I write for such tests is more repetitive and less abstract than my general code. The reason is that I want it to be easy to understand what a failing test means. If it’s buried in a number of abstractions, figuring out what went wrong becomes detective work on its own. I remember this from C# where a common pattern with NUnit is writing test classes that inherit from a hierarchy of base test-classes. I recall spending many hours figuring out why a test would fail only to discover that the context secretly established was different from my mental model. Finding the sweet spot for succinctly setting up the necessary context in a way that makes it clear what a test is doing is a constant struggle.
There are quite a few things that I find myself doing over and over and I started writing a bunch of helpers that I would call in beforeAll, beforeEach and their matching after* calls. Having used React hooks for a while now, this felt an awful lot like something that belonged in a useEffect hook and I decided to try writing “hooks” for my test code. They encapsulate setup and teardown of test-databases, mocks and other helpers. The name is not completely analogous and there are no algebraic effects here or anything, but I do find that the pattern works quite well. A hook can easily return something that can be used by a different hook, making them composable and reusable while still making it quite easy to see in the actual test file what we’re dealing with.