Learn C# - Testing & Quality Assurance
Series/Learn C#/Episode 10
Episode 10 of 23

Learn C# - Testing & Quality Assurance

This episode covers code quality: unit testing with xUnit, mocking with Moq, integration testing with WebApplicationFactory, and static analysis with Roslyn analyzers and quality tooling such as dotnet format.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

An untested application is a time bomb. The bigger the codebase, the more expensive it is to fix bugs that slip into production. Testing is the safety net that gives you the courage to change code without fearing you'll break something else.

In the .NET ecosystem, the testing ecosystem is very mature: xUnit for unit testing, Moq for mocking, and WebApplicationFactory for integration testing at the HTTP level.

Episode 10 teaches those three testing layers, plus static analysis and analyzers to maintain quality without waiting for bugs to happen.

Unit Testing with xUnit

Creating a Test Project

xUnit is the most popular testing framework in .NET. The test project is created separately from the application code, following the tests structure convention:

Creating an xUnit project
dotnet new xunit -n Toko.Tests -o tests/Toko.Tests
dotnet add tests/Toko.Tests reference src/Toko.App
dotnet sln Toko.sln add tests/Toko.Tests

The dotnet add ... reference src/Toko.App command connects the test project to the application project under test. Without this reference, the tests can't see the code being tested.

Writing Facts and Theories

xUnit has two styles: Fact for a single specific case, and Theory for testing many inputs with one test:

Fact and Theory
public class KalkulatorTests
{
    [Fact]
    public void Tambah_DuaAngka_ReturnsJumlah()
    {
        var hasil = Kalkulator.Tambah(2, 3);
        Assert.Equal(5, hasil);
    }
 
    [Theory]
    [InlineData(1, 1, 2)]
    [InlineData(10, 5, 15)]
    [InlineData(-1, 1, 0)]
    public void Tambah_BerbagaiInput_ReturnsJumlah(int a, int b, int expected)
    {
        Assert.Equal(expected, Kalkulator.Tambah(a, b));
    }
}

Run all tests with:

Running tests
dotnet test

The dotnet test command builds the test project and runs all tests, showing a summary of passes and failures. Descriptive test names like Tambah_DuaAngka_ReturnsJumlah become living documentation of behavior.

Mocking with Moq

Replacing External Dependencies

Unit tests must be fast and deterministic, so external dependencies like databases or APIs are mocked. Moq creates substitute objects whose behavior you can control:

Mocking with Moq
public interface IRepository
{
    Task<Produk> GetByIdAsync(int id);
}
 
[Fact]
public async Task GetProduk_ProdukDitemukan_ReturnsProduk()
{
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(r => r.GetByIdAsync(1))
        .ReturnsAsync(new Produk { Id = 1, Nama = "Kopi" });
 
    var service = new ProdukService(mockRepo.Object);
    var hasil = await service.GetProduk(1);
 
    Assert.Equal("Kopi", hasil.Nama);
}

mockRepo.Setup(r => r.GetByIdAsync(1)) determines the answer when the method is called. With a mock, you test ProdukService logic without ever touching a real database.

Integration Testing with WebApplicationFactory

Testing the Full HTTP Stack

An integration test runs a real web application in-process and sends real HTTP requests. WebApplicationFactory provides an HttpClient connected to the application:

Integration test Web API
public class ProdukApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
 
    public ProdukApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }
 
    [Fact]
    public async Task GetProduk_ReturnsOk()
    {
        var response = await _client.GetAsync("/api/produk/1");
        response.EnsureSuccessStatusCode();
    }
}

This integration test proves that routing, dependency injection, and the HTTP pipeline work together. For the database in tests, swap the provider for SQLite or a test container.

Static Analysis and Code Quality

Roslyn Analyzers and dotnet format

Roslyn analyzers detect problematic patterns in code as you write, not at runtime. Some built-in SDK analyzers are enabled by default. Run the quality checks:

Formatting and analyzing code
dotnet format
dotnet format --verify-no-changes
dotnet build -warnaserror

The dotnet format command tidies code style according to configuration, while --verify-no-changes ensures the code is already clean — very useful in CI. For deeper analysis, tools like SonarQube combine analyzers, coverage, and duplication detection in the pipeline.

Closing

Key takeaways:

  • xUnit with Fact and Theory is the foundation of .NET unit testing.
  • Moq replaces external dependencies so tests are fast and deterministic.
  • WebApplicationFactory tests the full HTTP stack including DI and routing.
  • dotnet test runs all tests and gives a summary.
  • Roslyn analyzers and dotnet format keep quality from the start.

In the next episode 11 we enter the web world: web API and HTTP client — the basics of ASP.NET Core Web API, controllers and routing, model binding, HttpClient with typed clients, and API versioning and standard error responses.

Learn C# - Testing & Quality Assurance | Learn C#