Advanced 65 min.NET 10

Tests that protect real behavior

Use isolated integration tests to verify authentication, ownership, validation, and persistence.

BY THE END OF THIS TUTORIAL

Here's what you'll be able to do.

  • Choose the right boundary for a test
  • Use WebApplicationFactory with isolated storage
  • Assert both HTTP behavior and resulting database state
Before you begin

Complete the TaskBoard feature lessons. The example ZIP includes TaskBoard.Tests next to the app project.

Understand the idea

A useful test protects behavior you care about. For this application, that means invalid input never creates a task, one user cannot read or change another user’s data, and a valid write is persisted. A test that duplicates the implementation line by line provides much less confidence.

Integration tests run the application through its HTTP pipeline using a test host. They are particularly valuable when behavior depends on middleware, authentication, model binding, routing, and EF working together.

Choose your environment

Open TaskBoard.csproj from the example download. Install the ASP.NET and web development workload and a Visual Studio release supporting .NET 10. Select the TaskBoard launch profile and press F5. Use Solution Explorer to find the files named below.

Download the complete companion project
STEP 01

Run the included suite

From the extracted download directory, run the test project. It references the application and Microsoft.AspNetCore.Mvc.Testing. WebApplicationFactory starts an in-process host, and the test setup supplies a dedicated temporary SQLite database.

The public partial Program declaration makes the application entry point accessible to the factory. The tests use their own configuration and do not connect to the database you created while following the lessons.

Terminal
dotnet test TaskBoard.Tests/TaskBoard.Tests.csproj --nologo

Run this from the download’s top-level directory, where TaskBoard and TaskBoard.Tests are siblings.

STEP 02

Separate authentication setup from ownership logic

A controlled test authentication handler can represent different user ids without posting credentials in every test. Seed matching Identity users into the isolated database so foreign keys remain valid. Then send requests as A and B to exercise ownership.

This verifies the application’s authorization and query boundaries, but it does not prove every production cookie or email flow. Keep a smaller set of real account-flow tests or manual checks for registration, login, sign-out, and recovery. Be clear about what a test replaces.

Example assertion
using var response = await client.GetAsync($"/api/tasks/{otherUsersTaskId}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);

// Also query the isolated database when testing a mutation:
// Assert that the original title and owner are unchanged.
STEP 03

Exercise forms through HTTP

A realistic form test first GETs the form, extracts its antiforgery token, and retains the cookie. The POST includes both. This tests model binding and antiforgery behavior instead of calling the action method directly.

For invalid input, assert the validation message and verify the task count did not change. For valid input, assert the redirect and then inspect the database. For a missing token, expect rejection before the action writes.

Form test outline
GET /Tasks/Create
→ keep the antiforgery cookie
→ extract __RequestVerificationToken
POST /Tasks/Create with token and fields
→ assert response status
→ query the test database
→ assert the intended row change, or no change
STEP 04

Use a database that tests the right behavior

The companion tests use SQLite, the same provider as the sample. An in-memory fake provider would not prove relational constraints or SQL translation. Even SQLite cannot guarantee SQL Server-specific behavior, so a production application should test important queries against its actual provider.

Keep tests isolated and deterministic. Use unique database paths, dispose the host and connections, and clean up test files. A test that passes only because another test created a row is not protecting the behavior you think it is.

Test review checklist
Does the test fail if the owner filter is removed?
Does invalid input leave the database unchanged?
Does a missing token prevent the write?
Can the suite run twice without relying on previous data?
Is the production database completely out of scope?
YOUR TURN

Make it your own.

In a disposable copy, temporarily remove the API owner predicate and run the tests. Confirm the ownership test fails, then restore the predicate and rerun.

See the solution & reasoning

A meaningful security regression test should detect this change. If the suite still passes, inspect whether it creates at least two users and attempts access to the other user’s row. The happy path alone cannot prove isolation.

When something doesn't work

If WebApplicationFactory cannot find the content root, check the project reference and test-host setup. The included suite explicitly locates the companion project.

A 400 during a form test may be an antiforgery failure before model validation. Obtain the token and keep its cookie.

A fake authenticated identity still needs a corresponding database user when the model enforces an owner foreign key.

CHECK YOUR UNDERSTANDING

Which assertion best protects an ownership boundary?

Which assertion best protects an ownership boundary?

ONE STEP FURTHER

That's another skill in your toolkit.

Your progress is saved in this browser. Sign in to save completion to your account.