🧪 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)
- Arrange 🎬 - Set up your toys
- Act 🎯 - Play with them
- 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! 🎉
