NJ Municipality Lookup
CodebaseSrc__tests__

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:

  1. Avoid External Dependencies: Test without calling real APIs
  2. Reduce Boilerplate: Concise test data creation
  3. Consistent Test Data: Shared fixtures across test suite
  4. 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?

FakesMocks
Working implementationsJust record calls
Test actual behaviorTest interaction patterns
Resilient to refactoringBrittle (break on internal changes)
Realistic test coverageOnly 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: Throw AddressNotFoundError
  • shouldTimeout: Throw ApiTimeoutError

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 correctly
  • test-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

  1. Use Fakes for Ports: Test against interfaces, not implementations
  2. Use Real Addresses: Generators use actual NJ addresses for realism
  3. Override Defaults: Generators accept overrides for specific scenarios
  4. Test Helpers Too: Ensure test utilities are themselves reliable
  5. Keep Simple: Helpers should be simpler than code under test

On this page