Testing in C#: Unit, Integration, E2E from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Testing in C#?

\n

In modern C# (.NET) development, code quality is not a luxury, but a necessity. Testing not only helps catch bugs before they reach production but also serves as living documentation and accelerates refactoring. There are three main levels of testing that form the "test pyramid": Unit, Integration, and E2E. In this article, we will break down each of them with real C# code examples.

\n\n

1. Unit Testing: The Foundation of Quality

\n

Unit tests verify an isolated piece of code (method, class) without external dependencies (databases, file systems, APIs). In the .NET ecosystem, three frameworks are popular: xUnit (recommended for new projects), NUnit, and MSTest.

\n\n

Core Principles (FIRST)

\n
    \n
  • Fast — fast (milliseconds).
  • \n
  • Isolated — isolated (use mocks).
  • \n
  • Repeatable — repeatable.
  • \n
  • Self-validating — self-validating (Assert).
  • \n
  • Timely — timely.
  • \n
\n\n

Unit Test Example with xUnit and Moq

\n

Suppose we have a service for calculating discounts:

\n
public interface IDiscountService\n{\n    decimal CalculateDiscount(decimal price, bool isLoyaltyMember);\n}\n\npublic class DiscountService : IDiscountService\n{\n    public decimal CalculateDiscount(decimal price, bool isLoyaltyMember)\n    {\n        if (isLoyaltyMember) return price * 0.9m; // 10% discount\n        return price;\n    }\n}
\n

Test using Moq (for mocking dependencies) and xUnit:

\n
using Xunit;\nusing Moq;\n\npublic class DiscountServiceTests\n{\n    [Fact]\n    public void CalculateDiscount_WhenLoyaltyMember_ReturnsDiscountedPrice()\n    {\n        // Arrange\n        var service = new DiscountService();\n        var price = 100m;\n\n        // Act\n        var result = service.CalculateDiscount(price, true);\n\n        // Assert\n        Assert.Equal(90m, result);\n    }\n\n    [Theory]\n    [InlineData(100, false, 100)]\n    [InlineData(200, true, 180)]\n    public void CalculateDiscount_MultipleCases_ReturnsCorrectValues(decimal price, bool isLoyalty, decimal expected)\n    {\n        var service = new DiscountService();\n        var result = service.CalculateDiscount(price, isLoyalty);\n        Assert.Equal(expected, result);\n    }\n}
\n

Tip: Use the [Fact] attribute for single tests and [Theory] + [InlineData] for parameterized tests.

\n\n

2. Integration Testing: Verifying Component Interaction

\n

Integration tests check how multiple modules work together — for example, a controller, a service, and a real database (or its substitute, such as in-memory SQLite). In ASP.NET Core, WebApplicationFactory is used for this purpose.

\n\n

Setting Up WebApplicationFactory

\n

Let's create a test class that spins up a test web host:

\n
using Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing MyApp.Data;\n\npublic class CustomWebApplicationFactory : WebApplicationFactory<Program>\n{\n    protected override void ConfigureWebHost(IWebHostBuilder builder)\n    {\n        builder.ConfigureServices(services =>\n        {\n            // Remove the real DbContext\n            var descriptor = services.SingleOrDefault(\n                d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));\n            if (descriptor != null) services.Remove(descriptor);\n\n            // Add in-memory database\n            services.AddDbContext<AppDbContext>(options =>\n                

Blogs

Book Recommendations