How do you make a Web API testable?
Quick Answer
Keep controllers/endpoints thin — bind, delegate to a service or handler, map the result to a status code. The logic lives in injectable services you unit-test in isolation. Test the HTTP layer (routing, filters, serialization, auth) with an integration test via WebApplicationFactory.
The main thing is keeping the controller thin — it binds the request, calls a service or a handler, and turns the result into a status code. All the actual logic is in injectable services, and those I unit-test on their own with the dependencies mocked, which is fast and lets me cover lots of edge cases. Then I write a handful of integration tests through WebApplicationFactory to prove the HTTP plumbing works — routing, filters, serialization, auth. If the controller has a DbContext and business rules in it, it's hard to test and that's the smell.
- A controller action should be a few lines:
var result = await _service.DoAsync(dto); return result.IsSuccess ? Ok(result.Value) : NotFound();. - No
DbContext, no business rules, nonew-ing dependencies in the controller. - Unit-test the service with mocked dependencies (fast, many cases); integration-test a few end-to-end happy/sad paths.
- MediatR makes this natural — the controller sends a request, the handler is the unit under test.