Tests let you refactor with confidence and are expected in every professional .NET codebase.
.NET has three popular test frameworks: xUnit, NUnit and MSTest. They work the same way: a separate test project references the code under test, and dotnet test discovers and runs the tests.
dotnet new xunit -o Calc.Tests
dotnet add Calc.Tests reference Calc
dotnet testIn xUnit:
[Fact]marks a test method with no parameters.[Theory]with[InlineData(...)]runs the same test for several inputs.Assert.Equal(expected, actual),Assert.True,Assert.Null,Assert.Contains,Assert.Throws<T>(() => ...), andawait Assert.ThrowsAsync<T>(...)for async code.- A new instance of the test class is created for each test, so the constructor is per-test setup and
IDisposable.Disposeis teardown. Share expensive setup with class fixtures (IClassFixture<T>).
Follow Arrange, Act, Assert: set up inputs, call the method, check the result. Name tests after behavior (Withdraw_MoreThanBalance_Throws).
To isolate code from databases or HTTP, depend on interfaces and pass fakes; mocking libraries such as NSubstitute or Moq generate them. Test async methods by making the test async Task.
Run a subset with dotnet test --filter "FullyQualifiedName~Calc" and collect coverage with --collect:"XPlat Code Coverage".
Example
// Calc.Tests/CalculatorTests.cs (xUnit project referencing Calc)
using Xunit;
public class CalculatorTests
{
private readonly Calculator _calc = new(); // fresh per test
[Fact]
public void Add_TwoNumbers_ReturnsSum()
=> Assert.Equal(5, _calc.Add(2, 3)); // Act + Assert
[Theory]
[InlineData(10, 2, 5)]
[InlineData(9, 3, 3)]
public void Divide_ReturnsQuotient(int a, int b, int expected)
=> Assert.Equal(expected, _calc.Divide(a, b));
[Fact]
public void Divide_ByZero_Throws()
=> Assert.Throws<DivideByZeroException>(() => _calc.Divide(1, 0));
}
public class Calculator // normally in the Calc project
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b) => a / b;
}Remember
- Tests live in a separate project; dotnet test runs them
- [Fact] for single cases, [Theory] + [InlineData] for tables of cases
- xUnit creates a new test class instance per test
- Depend on interfaces so tests can substitute fakes
Watch out for
- Tests that depend on each other's state or on execution order.