By Wallace Ferreira2 min read
Testing Use Cases with In-Memory Repositories
When a stub per method is enough, and when a small repository that keeps its data in memory makes page tests describe state instead of calls.
On this page
A use case only talks to its ports, so a test only has to provide them. The question is how: a stub per method, or a small working repository that keeps its data in memory.
Stubs are fine for one call
The application tests here build a repository from vi.fn stubs and override the method the use
case calls:
export function makeBlogPostRepository(
overrides: Partial<IBlogPostRepository> = {},
): IBlogPostRepository {
return { findAll: vi.fn(), findBySlug: vi.fn(), ...overrides };
}That works when one use case makes one call. It gets brittle when a page composes several use
cases over the same repository: each test has to know which method every use case reaches for,
and a refactor from findBySlug to findAll breaks tests even though the page still renders
the same thing.
An in-memory repository is just another adapter
The port is the contract
export interface IBlogPostRepository {
findAll(): Promise<BlogPost[]>;
findBySlug(slug: Slug): Promise<BlogPost | null>;
}Prisma implements it in @repo/infra. Nothing stops a test from implementing it too.
The test double
The page tests keep the posts in a variable and answer from it, so the test sets up state, not calls:
let posts: BlogPost[] = [];
let failure: Error | null = null;
const inMemoryRepository: IBlogPostRepository = {
findAll: async () => {
if (failure) throw failure;
return posts;
},
findBySlug: async () => null,
};Setting failure simulates a database outage without touching the use cases. The
FakeAuthenticationGateway in the identity tests follows the same idea with simulateError().
Data from a builder
BlogPostBuilder from @repo/core/testing creates valid domain entities, so the double only
ever holds posts that could exist in production:
beforeEach(() => {
posts = [
blogPost('older-post', '2026-08-01T10:00:00.000Z').now(),
blogPost('draft-post', '2026-09-20T10:00:00.000Z', BlogPostStatus.DRAFT).now(),
];
repo.findAll.mockResolvedValue([olderPost, draftPost]);
});What it buys you
- Tests describe a situation ("one draft, one published post") instead of a call sequence.
- Use cases can change which repository method they call without breaking page tests.
- The real filtering, sorting and mapping run, so the test checks behaviour, not wiring.
Keep the stubs for unit tests of a single use case. Reach for the in-memory version when the test covers a page or a flow.
About the author