Unit tests
Extract pure domain logic
Move validation and transformation rules into small functions whose outputs depend only on explicit inputs.
8 minute lesson
Pure logic is cheap to test because it does not need a server, database, clock, network, or cleanup.
Extract book normalization and validation from the route handler. Return a value or a typed result instead of mutating global state. Keep HTTP status selection in the HTTP layer.
export function normalizeBook(input) {
const title = input.title?.trim() ?? ''
const author = input.author?.trim() ?? ''
const year = input.year === '' || input.year == null
? null
: Number(input.year)
if (!title) return { ok: false, field: 'title', reason: 'required' }
if (!author) return { ok: false, field: 'author', reason: 'required' }
if (year !== null && !Number.isInteger(year)) {
return { ok: false, field: 'year', reason: 'integer' }
}
return { ok: true, value: { title, author, year } }
}
This function owns a domain decision: what counts as a valid book value. The HTTP handler owns a transport decision: turning a failed result into a 400 JSON response. Keeping those decisions separate prevents a unit test from needing to construct a Request, and prevents the domain function from knowing about Hono.
Purity is not a goal by itself. Do not extract a one-line wrapper merely to mock it. Extract logic when explicit inputs and outputs make an important rule easier to see. The remaining route still needs an integration test, because a perfectly tested normalizer does not prove that the handler calls it or serializes its result correctly.
Create and test a function that trims title and author, validates length, and normalizes an optional year. Include one test that proves the input object is not mutated and one HTTP test that proves a validation failure becomes the documented response.
Lesson completed