CodeOath
← All posts
.NET Core / Web API70 min total · 19 parts

Building REST APIs with ASP.NET Core: Routing, Middleware, and Dependency Injection

Contents — Part 18 of 19: Testing Web API Controllers
Part 18 of 19 · ~1 min

Testing Web API Controllers

Because DI is central to how ASP.NET Core wires everything together, controllers are naturally testable in isolation by constructing them directly with mocked dependencies, without spinning up the actual web server:

[Fact]
public async Task GetById_ReturnsNotFound_WhenOrderDoesNotExist()
{
    var mockService = new Mock<IOrderService>();
    mockService.Setup(s => s.GetByIdAsync(1)).ReturnsAsync((Order?)null);

    var controller = new OrdersController(mockService.Object);
    var result = await controller.GetById(1);

    Assert.IsType<NotFoundResult>(result.Result);
}

This is exactly why the "controllers thin, logic in injected services" guideline (mentioned again in the mistakes below) matters beyond code organization — a controller that calls _orderService.GetByIdAsync can be tested by mocking IOrderService, in complete isolation from any real database. A controller with business logic and raw DbContext calls inline can't be unit tested the same way; it needs a much heavier integration test (via WebApplicationFactory, which spins up the whole app in-memory) just to exercise a single branch of logic.

For true end-to-end coverage — verifying routing, model binding, middleware, and the real pipeline together — WebApplicationFactory<TEntryPoint> hosts the actual app in-memory and issues real HTTP requests against it within a test, typically swapping the real database for an in-memory or test one via its service-overriding hooks.