9. Testing ASP.NET Core

ASP.NET Core Testing - Complete Guide

Practical Guide + Technical Reference


📋 Table of Contents

Part 1: Practical Guide (Hands-On)

  1. Testing Fundamentals

  2. Unit Testing (3 Approaches)

  3. Integration Testing (3 Approaches)

  4. Testing Controllers & APIs

  5. Testing with Database (DbContext)

  6. Mocking and Test Doubles

  7. Common Testing Patterns

  8. Troubleshooting Common Issues

  9. Best Practices

Part 2: Technical Reference (Deep Dive)

  1. Important Interfaces & Classes Reference

  2. Configuration Deep-Dive

  3. Advanced Testing Topics

  4. Performance Testing Basics


PART 1: PRACTICAL GUIDE


1. Testing Fundamentals

Simple Definition: Testing is writing code to verify your application works correctly.

Think of it like: Quality control inspectors checking products before they ship. Each test checks one thing works as expected.

The Testing Pyramid

Key Point: Most tests should be unit tests (fast, isolated), fewer integration tests (test components together), and minimal UI/E2E tests.


Test Types Overview

Test Type
What It Tests
Speed
Complexity
Quantity

Unit

Single method/class

⚡ Very Fast

Simple

Many (70%)

Integration

Multiple components

⏱️ Medium

Medium

Some (20%)

E2E

Complete user flows

🐌 Slow

Complex

Few (10%)


xUnit ⭐ RECOMMENDED (Official .NET recommendation)

  • Modern, clean API

  • Parallel test execution

  • No [TestClass] attributes needed

  • Used by ASP.NET Core team

NUnit

  • Similar to JUnit/Jest

  • Rich assertion library

  • Good for complex scenarios

MSTest

  • Built into Visual Studio

  • Good for simple scenarios

  • Less features than xUnit/NUnit

This guide uses xUnit for examples


2. Unit Testing (3 Approaches)

Method 1: Simple Tests (No Framework Setup)

When to use:

  • ✅ Testing pure functions

  • ✅ Testing business logic classes

  • ✅ Quick prototyping

  • ❌ Testing controllers/services with dependencies

Step 1: Install xUnit

Step 2: Create Test Class

Step 3: Run Tests


Method 2: Testing with Dependencies (Mocking)

When to use:

  • ✅ Testing services with dependencies

  • ✅ Need to control external behavior

  • ✅ Production code

Step 1: Install Moq

Step 2: Create Service to Test

Step 3: Write Tests with Mocks


Method 3: Testing with Test Fixtures (Shared Setup)

When to use:

  • ✅ Expensive setup (database, files)

  • ✅ Shared resources across tests

  • ✅ Need cleanup after tests

Step 1: Create Fixture

Step 2: Use Fixture in Tests


3. Integration Testing (3 Approaches)

Method 1: WebApplicationFactory (Standard Approach)

When to use:

  • ✅ Testing HTTP endpoints

  • ✅ Full request/response cycle

  • ✅ Middleware testing

Step 1: Install Required Packages

Step 2: Create Test Project Setup

Note: Make Program class accessible in your API project:


When to use:

  • ✅ Need custom configuration

  • ✅ Replace real services with test doubles

  • ✅ Use test database

  • ✅ Production testing scenarios

Step 1: Create Custom Factory

Step 2: Create Fake Services

Step 3: Use Custom Factory


Method 3: Integration Tests with Authentication

When to use:

  • ✅ Testing protected endpoints

  • ✅ Testing authorization

  • ✅ Role-based access

Step 1: Create Test Authentication Handler

Step 2: Create Factory with Test Auth

Step 3: Test Protected Endpoints


4. Testing Controllers & APIs

Testing Controller Actions Directly

Simple Controller Test:

Controller Unit Tests:


5. Testing with Database (DbContext)

Method 1: In-Memory Database (Quick Tests)

When to use:

  • ✅ Fast unit tests

  • ✅ Simple queries

  • ❌ Testing raw SQL

  • ❌ Testing database-specific features


Method 2: SQLite In-Memory (Better for Testing)

When to use:

  • ✅ More realistic database behavior

  • ✅ Testing complex queries

  • ✅ Testing migrations

  • ✅ Production-like tests


Method 3: Real Database with Docker (Integration Tests)

When to use:

  • ✅ Full integration testing

  • ✅ Testing database-specific features

  • ✅ Testing migrations on real DB

  • ✅ CI/CD pipelines

Step 1: docker-compose.test.yml

Step 2: Test Base with Real DB

Step 3: Run Tests


6. Mocking and Test Doubles

Types of Test Doubles

Using Moq - Complete Guide

Basic Setup:

Common Moq Patterns:

Advanced Mocking Scenarios:


7. Common Testing Patterns

Arrange-Act-Assert (AAA) Pattern


Testing Exceptions


Testing Collections


Data-Driven Tests with Theory


Test Setup and Cleanup


8. Troubleshooting Common Issues

Problem: Tests Pass Individually but Fail Together

Cause: Shared state between tests

Solution:


Problem: DbContext Tracking Issues

Cause: EF Core caches entities

Solution:


Problem: Async Tests Don't Wait

Cause: Forgot await or Task.Run

Solution:


Problem: Mock Not Working as Expected

Cause: Setup doesn't match actual call

Solution:


Problem: Integration Tests are Slow

Solutions:


9. Best Practices

✅ DO:

  1. Follow AAA Pattern

  2. Use Descriptive Test Names

  3. Test One Thing Per Test

  4. Keep Tests Fast

    • Unit tests < 100ms

    • Integration tests < 1s

    • Use in-memory databases

    • Mock external dependencies

  5. Make Tests Independent

    • No shared state

    • Can run in any order

    • Can run in parallel


❌ DON'T:

  1. Don't Test Implementation Details

  2. Don't Use Real External Services

  3. Don't Ignore Test Failures

    • Fix failing tests immediately

    • Don't comment out failing tests

    • Don't skip tests without reason

  4. Don't Copy-Paste Tests

    • Use Theory for similar tests

    • Extract common setup to helper methods

    • Use test fixtures for shared resources


Test Organization


Code Coverage Guidelines

Aim for:

  • Critical paths: 100%

  • Business logic: 90-100%

  • Controllers: 80%+

  • Overall: 70-80%+

Check coverage:

Generate reports:


PART 2: TECHNICAL REFERENCE


10. Important Interfaces & Classes Reference

xUnit Attributes

Attribute
Purpose
Example

[Fact]

Simple test

[Fact] public void Test()

[Theory]

Data-driven test

[Theory] [InlineData(1,2)]

[InlineData]

Provide test data

[InlineData(1, 2, 3)]

[MemberData]

External test data

[MemberData(nameof(Data))]

[ClassData]

Class-based data

[ClassData(typeof(TestData))]

[Trait]

Categorize tests

[Trait("Category", "Unit")]

[Skip]

Skip test

[Fact(Skip = "Reason")]


Assert Class (xUnit.Assert)

Namespace: Xunit

Usage Examples:


WebApplicationFactory

Namespace: Microsoft.AspNetCore.Mvc.Testing

Purpose: Create test server for integration tests

Members Table:

Member
Type
Purpose

CreateClient()

Method

Create HttpClient for tests

Services

Property

Access service provider

ConfigureWebHost()

Method

Override configuration

Server

Property

Access test server

WithWebHostBuilder()

Method

Configure builder

Complete Example:


Mock Class (Moq)

Namespace: Moq

Purpose: Create mock objects for testing

Methods Table:

Method
Purpose
Example

Setup()

Configure return value

Setup(m => m.Get()).Returns(value)

Returns()

Set return value

.Returns(value)

ReturnsAsync()

Set async return

.ReturnsAsync(value)

Throws()

Throw exception

.Throws<Exception>()

Callback()

Execute code

.Callback(() => count++)

Verify()

Check if called

Verify(m => m.Get(), Times.Once)

VerifyAll()

Verify all setups

Mock calls all configured setups

VerifyNoOtherCalls()

No unexpected calls

Ensures only verified calls made

Verification Times:


IClassFixture Interface

Namespace: Xunit

Purpose: Share setup/cleanup across tests in a class

Usage Pattern:


IAsyncLifetime Interface

Namespace: Xunit

Purpose: Async setup/cleanup for test classes

Usage:


11. Configuration Deep-Dive

Pattern 1: Inline Configuration (Simple Tests)

When to use: Quick tests, prototypes

Pros:

  • ✅ Simple and direct

  • ✅ Good for single tests

  • ✅ No configuration files

Cons:

  • ❌ Repeated code

  • ❌ Hard to maintain

  • ❌ Can't reuse configuration


Pattern 2: Base Class Setup (Reusable)

When to use: Multiple test classes need same setup


Pattern 3: Test Configuration Files (Production-Like)

When to use: Complex configuration, environment-specific settings

Step 1: appsettings.test.json

Step 2: Load Configuration in Tests


Configuration Comparison

Pattern
Complexity
Reusability
Maintenance
Production-Like

Inline

Simple

Low

Hard

❌ No

Base Class

Medium

High

Easy

⚠️ Partial

Config Files

Complex

High

Easy

✅ Yes


12. Advanced Testing Topics

Testing Middleware


Testing Background Services


Testing SignalR Hubs


Testing with Time/DateTime


Snapshot Testing

Install:

Usage:


13. Performance Testing Basics

BenchmarkDotNet Setup


Load Testing with NBomber


Summary: Complete Testing Checklist

Setup:

Unit Tests:

Integration Tests:

Test Quality:

Best Practices:


This completes the Testing guide combining practical hands-on content with deep technical reference!

Last updated