ASP.NET Core Web API

Unit Testing Controllers (xUnit & Moq)

1 Views Updated 5/4/2026

Unit Testing Controllers (xUnit & Moq)

Unit testing an API controller verifies its HTTP logic (Does it return a 404? Does it return a 200 OK?). It does not test the database or the network. To achieve this isolation, we use xUnit as our testing framework and Moq to create fake versions of our Repositories.

1. Setup & The AAA Pattern

Inside a separate Test Project, we install our packages. All tests follow the AAA (Arrange, Act, Assert) pattern.

dotnet new xunit -n MyApp.Tests
dotnet add package Moq

2. Writing the Test (Testing a 200 OK)

If we ask the controller for User ID #5, we must "Mock" the repository to pretend it found a user in a fake database, and then Assert that the controller correctly wrapped that user in an OkObjectResult.

using Xunit;
using Moq;
using Microsoft.AspNetCore.Mvc;

public class UsersControllerTests
{
    private readonly Mock<IUserRepository> _mockRepo;
    private readonly UsersController _controller;

    public UsersControllerTests()
    {
        // 1. ARRANGE: Create the Fake Repository
        _mockRepo = new Mock<IUserRepository>();
        
        // Inject the fake into the Controller!
        _controller = new UsersController(_mockRepo.Object);
    }

    [Fact]
    public async Task GetUser_ReturnsOkResult_WhenUserExists()
    {
        // ARRANGE: Instruct the mock what to return when asked for ID 5
        var fakeUser = new User { Id = 5, Name = "Sandeep" };
        _mockRepo.Setup(repo => repo.GetUserByIdAsync(5))
                 .ReturnsAsync(fakeUser);

        // ACT: Call the actual method on the Controller
        var result = await _controller.GetUser(5);

        // ASSERT: Verify the HTTP response type
        var okResult = Assert.IsType<OkObjectResult>(result.Result);
        
        // ASSERT: Verify the data payload inside the 200 OK
        var returnedUser = Assert.IsType<User>(okResult.Value);
        Assert.Equal(5, returnedUser.Id);
    }
}

3. Testing a Failure (Testing a 404 Not Found)

We must also test the fail states to ensure our API correctly communicates errors to the frontend.

[Fact]
public async Task GetUser_ReturnsNotFound_WhenUserDoesNotExist()
{
    // ARRANGE: Instruct the mock to return NULL when an unknown ID is requested
    _mockRepo.Setup(repo => repo.GetUserByIdAsync(999))
             .ReturnsAsync((User)null);

    // ACT: Call the controller
    var result = await _controller.GetUser(999);

    // ASSERT: Verify it returned exactly an HTTP 404 Not Found
    Assert.IsType<NotFoundResult>(result.Result);
}

4. Interview Mastery

Q: "Why do we mock the Repository instead of mocking the EF Core ApplicationDbContext directly?"

Architect Answer: "Mocking EF Core's `DbContext` and `DbSet` properties using Moq is notoriously difficult, brittle, and prone to breaking every time Microsoft updates EF Core. `DbSet` is incredibly complex because it implements `IQueryable`. By implementing the Repository Pattern, we abstract away EF Core entirely behind a simple `IUserRepository` interface. Testing becomes trivial because we simply Mock the interface methods (`Task GetUser()`) without caring at all about LINQ or SQL semantics."

ASP.NET Core Web API
1. Fundamentals & HTTP
Introduction to ASP.NET Core Web API REST Principles and HTTP Methods Controllers & ControllerBase Routing (Attribute vs Conventional) Action Return Types (IActionResult)
2. Request Handling
Model Binding (FromQuery, FromBody, FromRoute) Dependency Injection (DI) Deep Dive App Settings & The Options Pattern
3. Data Access & Architecture
EF Core Setup in Web API DbContext & Migrations Repository & Unit of Work Pattern Asynchronous Programming (async/await)
4. Data Transfer & Validation
Data Transfer Objects (DTOs) & AutoMapper Model Validation (DataAnnotations) FluentValidation Integration
5. Advanced Concepts
Global Exception Handling Middleware Content Negotiation (JSON vs XML) Pagination & Filtering Advanced Searching & Sorting HATEOAS (Hypermedia) Implementation Output Caching & Response Caching
6. Security & Authorization
Cross-Origin Resource Sharing (CORS) JWT Authentication Setup Access Tokens & Refresh Tokens Workflow Role-Based & Policy-Based Authorization API Key Authentication Rate Limiting & Throttling
7. Documentation & Testing
Swagger & OpenAPI Configuration Customizing API Documentation Unit Testing Controllers (xUnit & Moq) Integration Testing (WebApplicationFactory)
8. Microservices & Deployment
Consuming External APIs (IHttpClientFactory) Health Checks & Diagnostics API Versioning Strategies Deploying APIs (Docker & Azure)