Testing

Back

Loading concept...

🧪 Testing in ASP.NET Core: Your Code’s Safety Net

Imagine this: You build a beautiful sandcastle. But how do you know it won’t fall when a wave comes? You test it first with small splashes! That’s exactly what testing does for your code.


🎭 The Big Picture: Why Test?

Think of your ASP.NET app as a toy factory. Before shipping toys to kids, you check:

  • Does the toy work? ✅
  • Does it break easily? ❌
  • Does it do what it should? ✅

Testing = Quality Control for Code

graph TD A["Write Code"] --> B["Write Tests"] B --> C{Tests Pass?} C -->|Yes| D["🎉 Ship It!"] C -->|No| E["🔧 Fix Code"] E --> B

🎯 Unit Testing Controllers

What’s a Controller?

A controller is like the front desk at a hotel. Guests (users) ask questions, and the front desk gives answers.

What’s Unit Testing?

Testing one small piece at a time. Like checking if one light bulb works before testing the whole Christmas tree!

Example: Testing a Simple Controller

// Our Controller (The Front Desk)
public class HelloController
    : ControllerBase
{
    [HttpGet]
    public IActionResult SayHello()
    {
        return Ok("Hello, World!");
    }
}
// Our Test (Checking the Front Desk)
[Fact]
public void SayHello_Returns_Hello()
{
    // Arrange: Set up the desk
    var controller =
        new HelloController();

    // Act: Ask a question
    var result = controller
        .SayHello() as OkObjectResult;

    // Assert: Check the answer
    Assert.Equal("Hello, World!",
        result.Value);
}

The Three Steps (AAA Pattern)

  1. Arrange 🎬 - Set up your toys
  2. Act 🎯 - Play with them
  3. Assert ✅ - Check if they worked

🔧 Unit Testing Services

What’s a Service?

Services are the kitchen staff in a restaurant. The waiter (controller) takes orders, but the kitchen (service) does the actual cooking!

Example: Testing a Calculator Service

// Our Service (The Kitchen)
public class MathService
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}
// Our Test
[Fact]
public void Add_TwoPlusTwo_ReturnsFour()
{
    // Arrange
    var service = new MathService();

    // Act
    var result = service.Add(2, 2);

    // Assert
    Assert.Equal(4, result);
}

Why Test Services Separately?

  • 🎯 Find bugs faster
  • 🔍 Easier to understand what broke
  • 🚀 Tests run super fast!

🎭 Mocking in Tests

The Problem

What if your service needs a database? You can’t use a real database for every test—that’s slow and messy!

The Solution: Mocking!

A mock is like a pretend friend during playtime. It acts like the real thing but is much simpler!

// Real Database Service (Hard to test)
public interface IUserRepository
{
    User GetById(int id);
}

// Using Moq to Create a Pretend Friend
[Fact]
public void GetUser_Returns_User()
{
    // Create a pretend repository
    var mockRepo = new Mock<IUserRepository>();

    // Tell it what to do when asked
    mockRepo.Setup(r => r.GetById(1))
        .Returns(new User {
            Name = "Alice"
        });

    // Use the pretend in our test
    var service = new UserService(
        mockRepo.Object);

    var user = service.GetUser(1);

    Assert.Equal("Alice", user.Name);
}

Popular Mocking Libraries

Library Superpower
Moq Easy to use
NSubstitute Natural syntax
FakeItEasy Readable code

🧪 xUnit with ASP.NET Core

Why xUnit?

xUnit is the official testing friend of ASP.NET Core. It’s fast, modern, and powerful!

Key Features

1. [Fact] - A Single Test

[Fact]
public void True_IsTrue()
{
    Assert.True(true);
}

2. [Theory] - Many Tests, One Method

[Theory]
[InlineData(2, 2, 4)]
[InlineData(3, 3, 6)]
[InlineData(0, 5, 5)]
public void Add_Works(
    int a, int b, int expected)
{
    var result = a + b;
    Assert.Equal(expected, result);
}

3. Test Setup with Constructor

public class MyTests
{
    private readonly MyService _service;

    public MyTests()
    {
        // Runs before EACH test
        _service = new MyService();
    }
}

Running Tests

dotnet test

🌐 Integration Testing

Unit vs Integration Testing

Unit Test 🔬 Integration Test 🌐
Tests ONE piece Tests MANY pieces together
Super fast A bit slower
Uses mocks Uses real things

Why Integration Testing?

Unit tests check each LEGO brick. Integration tests check if the whole castle stands!

graph TD A["Integration Test"] --> B["Controller"] B --> C["Service"] C --> D["Database"] A --> E{Everything Works Together?}

🏭 WebApplicationFactory

What Is It?

WebApplicationFactory is a mini version of your app for testing. Like a toy model of a real car!

Basic Setup

public class MyAppTests : IClassFixture<
    WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public MyAppTests(
        WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Homepage_Returns_OK()
    {
        var response = await _client
            .GetAsync("/");

        Assert.Equal(
            HttpStatusCode.OK,
            response.StatusCode);
    }
}

Customizing Your Test App

public class CustomFactory :
    WebApplicationFactory<Program>
{
    protected override void
    ConfigureWebHost(
        IWebHostBuilder builder)
    {
        builder.ConfigureServices(
            services =>
        {
            // Swap real DB for test DB
            // Add test-only services
        });
    }
}

🖥️ Test Server

What’s a Test Server?

It’s like running your app in a testing bubble. The app thinks it’s real, but it’s actually in a controlled environment!

Using TestServer

[Fact]
public async Task Api_Returns_Data()
{
    // Build test host
    var builder = new WebHostBuilder()
        .UseStartup<Startup>();

    using var server =
        new TestServer(builder);
    using var client =
        server.CreateClient();

    // Make real HTTP requests!
    var response = await client
        .GetAsync("/api/products");

    response.EnsureSuccessStatusCode();

    var content = await response
        .Content.ReadAsStringAsync();

    Assert.Contains("product", content);
}

Test Server vs Real Server

Test Server 🧪 Real Server 🌍
In memory On network
Super fast Normal speed
For testing For users

💾 In-Memory Database Testing

The Challenge

Testing with a real database is:

  • 😰 Slow
  • 😵 Hard to set up
  • 🤯 Data gets messy

The Solution: In-Memory Database!

It’s a pretend database that lives in RAM. Fast, clean, and throws away data after each test!

Setting It Up

public class DatabaseTests
{
    private DbContextOptions<MyDb>
        GetInMemoryOptions()
    {
        return new DbContextOptionsBuilder<MyDb>()
            .UseInMemoryDatabase(
                databaseName: Guid.NewGuid()
                    .ToString())
            .Options;
    }

    [Fact]
    public void CanAddUser()
    {
        // Each test gets fresh database!
        var options = GetInMemoryOptions();

        using var context =
            new MyDb(options);

        context.Users.Add(
            new User { Name = "Bob" });
        context.SaveChanges();

        Assert.Equal(1,
            context.Users.Count());
    }
}

Pro Tip: Unique Database Names

// ✅ Good: Each test isolated
.UseInMemoryDatabase(
    Guid.NewGuid().ToString())

// ❌ Bad: Tests share data
.UseInMemoryDatabase("SharedDB")

🎁 Putting It All Together

Full Integration Test Example

public class ProductApiTests : IClassFixture<
    WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ProductApiTests(
        WebApplicationFactory<Program> factory)
    {
        _client = factory
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(
                    services =>
                {
                    // Remove real database
                    var descriptor = services
                        .SingleOrDefault(d =>
                        d.ServiceType ==
                        typeof(DbContextOptions<
                            AppDbContext>));

                    if (descriptor != null)
                        services.Remove(descriptor);

                    // Add in-memory database
                    services.AddDbContext<
                        AppDbContext>(opts =>
                    {
                        opts.UseInMemoryDatabase(
                            "TestDb");
                    });
                });
            })
            .CreateClient();
    }

    [Fact]
    public async Task
    CreateProduct_ReturnsCreated()
    {
        var product = new {
            Name = "Test",
            Price = 9.99
        };

        var response = await _client
            .PostAsJsonAsync(
                "/api/products", product);

        Assert.Equal(
            HttpStatusCode.Created,
            response.StatusCode);
    }
}

🏆 Testing Best Practices

DO ✅

  • Name tests clearly: Method_Scenario_Result
  • Keep tests independent
  • Test one thing per test
  • Use in-memory databases for speed

DON’T ❌

  • Share state between tests
  • Test private methods directly
  • Write tests after bugs ship
  • Forget to mock external services

🎯 Quick Reference

graph TD A["Testing Types"] --> B["Unit Tests"] A --> C["Integration Tests"] B --> D["Test Controllers"] B --> E["Test Services"] B --> F["Use Mocks"] C --> G["WebApplicationFactory"] C --> H["Test Server"] C --> I["In-Memory Database"]

🚀 Your Testing Journey Starts Now!

Remember:

  • Unit tests = Check each brick 🧱
  • Integration tests = Check the whole castle 🏰
  • Mocks = Pretend friends that help 🤖
  • In-memory DB = Fast, clean testing 💨

“Code without tests is like a car without brakes. It might go fast, but you can’t stop safely!” 🚗

Happy Testing! 🎉

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.