Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

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 TimeProvider for 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 Task and await it, including await 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 → WebApplicationFactory runs the app in memory and gives the test an HttpClient, 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

Edge cases, each side of a limit, and the bug you just fixed. Watch it fail first.

Create it, reference the code, write a [Fact], and run it from the terminal or the IDE.

The shape of every test, and picking the assertion that gives the clearest failure message.

Theory with InlineData, MemberData and TheoryData, one test for many inputs.

Isolating what you test

Fakes and stubs you write yourself, NSubstitute when you need it, and when not to mock.

Take a TimeProvider instead of calling DateTime.Now, and the test controls the clock.

async Task tests, Assert.ThrowsAsync, and tests that pass because they never waited.

Real parts together

Real files in a temporary folder, and a real SQLite database per test.

The whole API in memory, called through HttpClient, with services and settings replaced for the test.

Unit and integration tests together, with a fake store, FakeTimeProvider and a test for concurrency.

When a test fails

Run the one failing test under the debugger and step into the code it calls.