Tests that protect real behavior
Use isolated integration tests to verify authentication, ownership, validation, and persistence.
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
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.
Install the .NET 10 SDK and C# Dev Kit. Open the TaskBoard folder in VS Code. Run dotnet restore, then dotnet run in the integrated terminal. Browse http://localhost:5300. Open files with Ctrl+P (Cmd+P on macOS).
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.
dotnet test TaskBoard.Tests/TaskBoard.Tests.csproj --nologoRun this from the download’s top-level directory, where TaskBoard and TaskBoard.Tests are siblings.
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.
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.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.
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 changeUse 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.
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?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.
Which assertion best protects an ownership boundary?
That's another skill in your toolkit.
Your progress is saved in this browser. Sign in to save completion to your account.