Skip to content

Aspnet Testing

DodaTech 4 min read

title: ASP.NET Core Testing — Complete Guide to xUnit and Integration Tests description: 'Learn ASP.NET Core testing: xUnit framework, unit testing services, integration testing with WebApplicationFactory, mocking, code coverage, and CI testing.' date: 2026-06-28 lastmod: 2026-06-28 weight: 29 tags: [backend, aspnet]


ASP.NET Core testing uses xUnit for unit tests and WebApplicationFactory for integration tests, enabling comprehensive test coverage from individual services to full HTTP request pipelines.

## What You'll Learn

By the end of this tutorial, you'll write xUnit unit tests, create integration tests with WebApplicationFactory, mock dependencies with Moq/NSubstitute, measure code coverage, and run tests in CI/CD.

## Real-World Use

A payment API has 500+ unit tests for service logic and 200+ integration tests for endpoint behavior. Every pull request runs the full suite. A failing test blocks the merge.

## Testing Learning Path

```mermaid
flowchart LR
  A[SignalR] --> B[Testing]
  B --> C[Logging]
  C --> D[Health Checks]
  D --> E[Docker]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

xUnit Unit Test

dotnet new xunit -n MyApp.Tests
cd MyApp.Tests
dotnet add reference ../src/MyApp/MyApp.csproj
using Xunit;
using Moq;
public class ProductServiceTests
{
    private readonly Mock<IProductRepository> _repoMock;
    private readonly ProductService _service;
    public ProductServiceTests()
    {
        _repoMock = new Mock<IProductRepository>();
        _service = new ProductService(_repoMock.Object);
    }
    [Fact]
    public async Task GetById_WhenProductExists_ReturnsProduct()
    {
        var expected = new Product { Id = 1, Name = "Test", Price = 10m };
        _repoMock.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(expected);
        var result = await _service.GetByIdAsync(1);
        Assert.NotNull(result);
        Assert.Equal(expected.Name, result.Name);
    }
    [Fact]
    public async Task GetById_WhenProductNotFound_ReturnsNull()
    {
        _repoMock.Setup(r => r.GetByIdAsync(99)).ReturnsAsync((Product?)null);
        var result = await _service.GetByIdAsync(99);
        Assert.Null(result);
    }
}

Integration Tests

using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    public ApiIntegrationTests(WebApplicationFactory<Program> factory) => _factory = factory;
    
    [Fact]
    public async Task GetProducts_ReturnsSuccess()
    {
        var client = _factory.CreateClient();
        var response = await client.GetAsync("/api/products");
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        Assert.Contains("products", content);
    }
    [Fact]
    public async Task GetProduct_WithInvalidId_ReturnsNotFound()
    {
        var client = _factory.CreateClient();
        var response = await client.GetAsync("/api/products/9999");
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }
}

Mocking with Moq

public class AuthServiceTests
{
    [Fact]
    public async Task Login_ValidCredentials_ReturnsToken()
    {
        var userManagerMock = new Mock<UserManager<IdentityUser>>(
            Mock.Of<IUserStore<IdentityUser>>(), null, null, null, null, null, null, null, null);
        var tokenServiceMock = new Mock<ITokenService>();
        userManagerMock.Setup(um => um.FindByEmailAsync("test@test.com"))
            .ReturnsAsync(new IdentityUser { Email = "test@test.com" });
        userManagerMock.Setup(um => um.CheckPasswordAsync(It.IsAny<IdentityUser>(), "pass"))
            .ReturnsAsync(true);
        tokenServiceMock.Setup(ts => ts.GenerateAccessToken(It.IsAny<IdentityUser>()))
            .Returns("fake-jwt-token");
        var service = new AuthService(userManagerMock.Object, tokenServiceMock.Object);
        var result = await service.LoginAsync("test@test.com", "pass");
        Assert.NotNull(result);
        Assert.Equal("fake-jwt-token", result.AccessToken);
    }
}

Custom WebApplicationFactory

public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Remove real DbContext
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
            if (descriptor != null) services.Remove(descriptor);
            // Add in-memory database
            services.AddDbContext<AppDbContext>(options =>
                options.UseInMemoryDatabase("TestDb"));
        });
    }
}

Common Mistakes

1. Testing Implementation Details

Test behavior, not implementation. Don't test private methods or internal logic.

2. Not Using IClassFixture

WebApplicationFactory is expensive to create. Use IClassFixture to share the factory across tests.

3. Forgetting Async

ASP.NET Core is async. Use async Task for test methods and await all async calls.

4. Mocking Everything

Integration tests should test real components with overridden infrastructure (in-memory DB, test file system).

5. Not Running Tests in CI

Tests only protect code when run automatically. Add dotnet test to CI pipeline.

Practice Questions

1. What is xUnit?

A free, open-source unit testing framework for .NET. The default test framework for ASP.NET Core projects.

2. How does WebApplicationFactory work?

It creates an in-memory instance of your application, allowing HTTP requests without a running server.

3. What is the purpose of mocking?

Mocking replaces real dependencies (database, API) with controlled test doubles that return predefined values.

4. How do you verify a method was called on a mock?

Use mock.Verify(m => m.MethodName(...), Times.Once) to assert expected interactions.

5. Challenge: Write an integration test that creates a product via POST and verifies it exists via GET.

[Fact]
public async Task CreateProduct_ThenGetById_ReturnsCreatedProduct()
{
    var client = _factory.CreateClient();
    var createResponse = await client.PostAsJsonAsync("/api/products", new { Name = "New", Price = 29.99m });
    createResponse.EnsureSuccessStatusCode();
    var created = await createResponse.Content.ReadFromJsonAsync<ProductDto>();
    var getResponse = await client.GetAsync($"/api/products/{created!.Id}");
    var product = await getResponse.Content.ReadFromJsonAsync<ProductDto>();
    Assert.Equal("New", product!.Name);
}

FAQ

What is the difference between [Fact] and [Theory] in xUnit?

Fact is a single test. Theory is parameterized with [InlineData] or [MemberData] for multiple inputs.

Should I use Moq or NSubstitute?

Both are popular. Moq is more established. NSubstitute has cleaner syntax. Choose based on team preference.

How do I test middleware?

Use WebApplicationFactory and check response headers/status codes for middleware behavior.

What is code coverage and how do I measure it?

Code coverage shows % of code executed by tests. Use Coverlet: dotnet test --collect:'XPlat Code Coverage'

How do I run tests in parallel?

xUnit runs tests in parallel by default (per test collection). Configure [Collection] for shared state.

Mini Project: Test Suite for Product API

Write unit and integration tests for a product API.

dotnet new xunit -n MyApp.Tests
dotnet add reference ../src/MyApp.Web/MyApp.Web.csproj
dotnet add package Moq
dotnet add package Microsoft.AspNetCore.Mvc.Testing
// Unit test
[Fact]
public async Task GetAll_ReturnsAllProducts() { ... }
// Integration test
[Fact]
public async Task GetAll_Endpoint_ReturnsOk() { ... }

What's Next

ASP.NET Core Logging ASP.NET Core Health Checks

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro