Helpers
Reusable test utilities including fake service implementations, test data generators, and common test fixtures.
Test Helpers (__tests__/helpers/)
Reusable test utilities including fake service implementations, test data generators, and common test fixtures.
Purpose
Provides testing infrastructure to:
- Avoid External Dependencies: Test without calling real APIs
- Reduce Boilerplate: Concise test data creation
- Consistent Test Data: Shared fixtures across test suite
- Deterministic Tests: Predictable results for reliable tests
Modules
fake-services.ts - Test Doubles
Fake implementations (not mocks) of port interfaces for dependency injection in tests.
Why Fakes Over Mocks?
| Fakes | Mocks |
|---|---|
| Working implementations | Just record calls |
| Test actual behavior | Test interaction patterns |
| Resilient to refactoring | Brittle (break on internal changes) |
| Realistic test coverage | Only verify method calls |
Fake Services Provided
createFakeGeocodingService()
Geocoding service that returns predictable results without API calls:
import { createFakeGeocodingService } from "@/__tests__/helpers/fake-services";
const geocoder = createFakeGeocodingService({
shouldFail: false, // Return success
shouldTimeout: false, // Don't simulate timeout
});
const address = createAddressInput("123 Main St, Newark, NJ");
const result = await geocoder.geocodeAddress(address);
expect(result.municipality.name).toBe("Newark");
Configuration Options:
shouldFail: ThrowAddressNotFoundErrorshouldTimeout: ThrowApiTimeoutError
createFakeSuggestionsService()
Suggestions service for autocomplete testing:
import { createFakeSuggestionsService } from "@/__tests__/helpers/fake-services";
const suggester = createFakeSuggestionsService({
results: [
createAddressSuggestion("123 Main St, Newark, NJ"),
createAddressSuggestion("456 Oak Ave, Newark, NJ"),
],
});
const suggestions = await suggester.getSuggestions("123");
expect(suggestions).toHaveLength(2);
createFakeCache()
In-memory cache for testing use cases:
import { createFakeCache } from "@/__tests__/helpers/fake-services";
const cache = createFakeCache();
// Set value
await cache.set("key", { data: "value" });
// Get value
const entry = await cache.get("key");
expect(entry?.value.data).toBe("value");
// Delete value
await cache.delete("key");
// Clear all
await cache.clear();
createFakeHttpClient()
HTTP client for testing adapters:
import { createFakeHttpClient } from "@/__tests__/helpers/fake-services";
const http = createFakeHttpClient({
response: { result: "success" },
shouldFail: false,
shouldTimeout: false,
});
const response = await http.get("/api/test");
expect(response.status).toBe(200);
expect(response.data.result).toBe("success");
test-data-generators.ts - Test Fixtures
Concise generators for creating domain entities with sensible defaults.
Real NJ Addresses
All generators use real New Jersey government addresses to ensure tests work with actual geocoding API:
export const REAL_NJ_ADDRESSES = {
njit: "323 Dr Martin Luther King Jr Blvd, Newark, NJ 07102",
stateHouse: "125 West State Street, Trenton, NJ 08608",
drumthwacket: "354 Stockton Street, Princeton, NJ 08540",
rutgers: "83 Somerset Street, New Brunswick, NJ 08901",
njcu: "2039 John F Kennedy Boulevard, Jersey City, NJ 07305",
} as const;
Why Real Addresses?
- Integration tests can use actual NJ API
- Generators produce valid test data
- Realistic edge cases (long street names, etc.)
- Easy to verify results manually
Generators Provided
genAddress()
Create address input entity:
import {
genAddress,
REAL_NJ_ADDRESSES,
} from "@/__tests__/helpers/test-data-generators";
// Default (NJIT address)
const address = genAddress();
// Specific address
const address = genAddress(REAL_NJ_ADDRESSES.stateHouse);
// Custom address
const address = genAddress("123 Main St, Newark, NJ");
genSuggestion()
Create address suggestion:
const suggestion = genSuggestion("123 Main St, Newark, NJ", 0.95);
expect(suggestion.text).toBe("123 Main St, Newark, NJ");
expect(suggestion.confidence).toBe(0.95);
genMunicipality()
Create municipality entity:
const muni = genMunicipality("Newark", "Essex", "City");
expect(muni.name).toBe("Newark");
expect(muni.county).toBe("Essex");
expect(muni.type).toBe("City");
genGeocodingResult()
Create geocoding result:
const result = genGeocodingResult({
address: REAL_NJ_ADDRESSES.njit,
municipality: genMunicipality("Newark", "Essex", "City"),
fromCache: true,
});
expect(result.municipality.name).toBe("Newark");
expect(result.fromCache).toBe(true);
genCacheEntry()
Create cache entry:
const entry = genCacheEntry({ data: "value" }, 7);
expect(entry.value.data).toBe("value");
expect(entry.key).toContain("geocoding:");
genAddresses()
Generate array of addresses for bulk testing:
const addresses = genAddresses(100);
expect(addresses).toHaveLength(100);
// Uses real addresses in rotation
expect(addresses[0]).toBe(REAL_NJ_ADDRESSES.njit);
expect(addresses[5]).toBe(REAL_NJ_ADDRESSES.njit);
Usage Patterns
Unit Test with Fakes
import {
createFakeGeocodingService,
createFakeCache,
} from "@/__tests__/helpers/fake-services";
import { genAddress } from "@/__tests__/helpers/test-data-generators";
test("should cache geocoding results", async () => {
const geocoder = createFakeGeocodingService();
const cache = createFakeCache();
const useCase = createLookupAddressUseCase(geocoder, cache);
const address = genAddress();
const result = await useCase.execute(address);
expect(result.fromCache).toBe(false);
// Second call should hit cache
const cached = await useCase.execute(address);
expect(cached.fromCache).toBe(true);
});
Integration Test with Real Data
import { REAL_NJ_ADDRESSES } from "@/__tests__/helpers/test-data-generators";
test("should geocode real NJ address", async () => {
const address = createAddressInput(REAL_NJ_ADDRESSES.njit);
const result = await realGeocodingService.geocodeAddress(address);
expect(result.municipality.name).toBe("Newark");
expect(result.municipality.county).toBe("Essex");
expect(result.formattedAddress).toContain("MARTIN LUTHER KING");
});
Bulk Processing Test
import { genAddresses } from "@/__tests__/helpers/test-data-generators";
test("should process 100 addresses", async () => {
const addresses = genAddresses(100).map(createAddressInput);
const results = await bulkGeocodingUseCase.execute(addresses);
expect(results).toHaveLength(100);
expect(results.every((r) => r.municipality)).toBe(true);
});
Error Scenario Test
import { createFakeGeocodingService } from "@/__tests__/helpers/fake-services";
test("should handle address not found", async () => {
const geocoder = createFakeGeocodingService({ shouldFail: true });
const address = genAddress();
await expect(geocoder.geocodeAddress(address)).rejects.toThrow(
AddressNotFoundError,
);
});
Timeout Scenario Test
import { createFakeHttpClient } from "@/__tests__/helpers/fake-services";
test("should handle API timeout", async () => {
const http = createFakeHttpClient({ shouldTimeout: true });
const client = createNjGeocodingClient(http);
await expect(client.geocodeAddress(genAddress())).rejects.toThrow(
ApiTimeoutError,
);
});
Testing the Helpers
Helpers themselves are tested to ensure reliability:
fake-services.test.ts: Verify fakes behave correctlytest-data-generators.test.ts: Ensure generators produce valid entities
// Test fake cache behavior
test("fake cache should store and retrieve values", async () => {
const cache = createFakeCache();
await cache.set("key", "value");
const entry = await cache.get("key");
expect(entry?.value).toBe("value");
});
// Test generator produces valid entity
test("genMunicipality should create valid entity", () => {
const muni = genMunicipality("Newark", "Essex", "City");
expect(muni.name).toBe("Newark");
expect(muni.county).toBe("Essex");
expect(muni.type).toBe("City");
});
Benefits
For Test Authors
- Less Boilerplate: One-line test data creation
- Readable Tests: Focus on "what" not "how"
- Consistency: Same fixtures across test suite
For Test Maintenance
- Centralized Changes: Update generators, not every test
- No External Dependencies: Fast, reliable tests
- Deterministic: Same input always produces same output
For Test Coverage
- Edge Cases: Easy to test error conditions
- Performance: No network calls, tests run fast
- Isolation: True unit tests (no external services)
Best Practices
- Use Fakes for Ports: Test against interfaces, not implementations
- Use Real Addresses: Generators use actual NJ addresses for realism
- Override Defaults: Generators accept overrides for specific scenarios
- Test Helpers Too: Ensure test utilities are themselves reliable
- Keep Simple: Helpers should be simpler than code under test
Related Documentation
- Test Suite Overview - All test types
- Domain Entities - Entities created by generators
- Domain Ports - Interfaces implemented by fakes
- Unit Testing Guide - Testing approach and patterns