Unit Testing Mastery (xUnit, NUnit, MSTest)
Write clean, maintainable tests that catch bugs before they reach production. Master the Arrange-Act-Assert pattern and advanced xUnit features.
What You'll Learn
- • Write tests with
[Fact]and parameterized[Theory] - • Master assertion methods for strings, collections, types, and exceptions
- • Use fixtures for shared setup with
IClassFixtureandICollectionFixture - • Follow the Arrange-Act-Assert pattern consistently
🧪 Real-World Analogy
Unit tests are like a pilot's pre-flight checklist. Before every flight, pilots verify each system individually — engines, instruments, hydraulics. They don't wait until they're airborne to discover problems. [Fact] tests check one specific thing. [Theory] tests run the same check with different conditions — like testing instruments at different altitudes.
xUnit Basics: Fact & Theory
xUnit is the most popular .NET test framework. [Fact] marks a single test case. [Theory] with [InlineData] runs the same test with different inputs. Always follow the Arrange-Act-Assert pattern.
xUnit Basics — Fact & Theory
Write your first unit tests with parameterized data and exception assertions.
using Xunit;
// Calculator.cs — the class we're testing
public class Calculator
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b)
{
if (b == 0) throw new DivideByZeroException("Cannot divide by zero");
return a / b;
}
public bool IsEven(int number) => number % 2 == 0;
}
// CalculatorTests.cs — xUnit test class
public class CalculatorTests
{
private readonly Calculator _calc = new();
// [Fact] — a single test case
[Fact]
...Assertion Methods Deep Dive
xUnit provides rich assertions for every data type — strings, collections, types, nullability, and async operations. Use the most specific assertion available for clearer failure messages.
Assertion Methods Showcase
Explore string, collection, type, async, and custom message assertions.
using Xunit;
using System.Collections.Generic;
public class AssertionShowcase
{
// String assertions
[Fact]
public void StringAssertions_DemonstrateOptions()
{
string greeting = "Hello, World!";
Assert.Equal("Hello, World!", greeting);
Assert.StartsWith("Hello", greeting);
Assert.EndsWith("World!", greeting);
Assert.Contains("World", greeting);
Assert.DoesNotContain("Goodbye", greeting);
Assert.Matches("[A-Z][a-z]+, [A-Z][
...Test Fixtures & Shared Setup
Use constructor/Dispose for per-test setup. For expensive resources (database, HTTP client), use IClassFixture (shared per class) or ICollectionFixture (shared across classes).
Fixtures — Constructor, Class & Collection
Share expensive setup across tests with IClassFixture and ICollectionFixture.
using Xunit;
using System;
// Constructor/Dispose pattern — fresh setup for each test
public class DatabaseTests : IDisposable
{
private readonly TestDatabase _db;
// Runs BEFORE each test
public DatabaseTests()
{
_db = new TestDatabase();
_db.Seed(); // Insert test data
}
[Fact]
public void GetUser_ExistingId_ReturnsUser()
{
var user = _db.GetUser(1);
Assert.NotNull(user);
Assert.Equal("TestUser", user.Name);
}
...Pro Tip
Name your tests with the pattern MethodName_Scenario_ExpectedResult (e.g., Add_TwoNegatives_ReturnsNegativeSum). This makes test failure reports self-documenting — you know exactly what broke without reading the test code.
Common Mistakes
- • Testing implementation instead of behavior — tests break on every refactor
- • Sharing mutable state between tests — causes flaky, order-dependent failures
- • Using
Assert.True(x == y)instead ofAssert.Equal(y, x)— worse error messages
Lesson Complete!
You've mastered unit testing fundamentals. Next, learn mocking frameworks to isolate dependencies in your tests.
Sign up for free to track which lessons you've completed and get learning reminders.