Every way to test your code
Unit tests, parameterized tests, test doubles and mocks, async tests, integration tests and WebApplicationFactory. What each kind proves, and how many of each to write.
Write the smallest test that would catch the bug you're worried about.
- A rule in one method (free shipping at $50) → a unit test with arrange, act, assert. One behaviour per test, named for the rule.
- The same rule at several inputs (49.99, 50, 50.01) → one
[Theory]with[InlineData]rows, not five copies of a[Fact]. - The code talks to a clock, an email sender or a payment
gateway → put that behind an interface (or
TimeProviderfor time) and hand the test a stand-in. A hand-written fake is often clearer than a mocking library; use NSubstitute or similar when you need to check a call was made. - The code is async → make the test
async Taskandawaitit, includingawait Assert.ThrowsAsync<T>(...)for failures. - Several real parts together (your code plus real files, or EF Core plus a real SQLite database) → an integration test, each with its own temporary folder or database.
- A web API, end to end →
WebApplicationFactoryruns the app in memory and gives the test anHttpClient, with services and settings swapped where you need.
Most tests should be fast unit tests, with fewer integration tests and a handful end to end. Watch each new test fail once before you trust it to pass.
Unit tests
Section
Why and what to test
Edge cases, each side of a limit, and the bug you just fixed. Watch it fail first.
Section
Your first xUnit test project
Create it, reference the code, write a [Fact], and run it from the terminal or the IDE.
Section
Arrange, act, assert
The shape of every test, and picking the assertion that gives the clearest failure message.
Section
Parameterized tests
Theory with InlineData, MemberData and TheoryData, one test for many inputs.
Isolating what you test
Section
Test doubles and mocking
Fakes and stubs you write yourself, NSubstitute when you need it, and when not to mock.
Section
Testable time with TimeProvider
Take a TimeProvider instead of calling DateTime.Now, and the test controls the clock.
Section
Testing async code
async Task tests, Assert.ThrowsAsync, and tests that pass because they never waited.
Real parts together
Section
Integration tests
Real files in a temporary folder, and a real SQLite database per test.
Section
WebApplicationFactory
The whole API in memory, called through HttpClient, with services and settings replaced for the test.
Section
The capstone's test suite
Unit and integration tests together, with a fake store, FakeTimeProvider and a test for concurrency.
When a test fails
Section
Debugging a failing test
Run the one failing test under the debugger and step into the code it calls.