- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Guided: Testing an ASP.NET Core 10 Application
Learn how to write and execute automated tests for a real ASP.NET Core 10 Web API application. In this guided lab, you'll set up a testing environment using xUnit and Moq, write unit tests to verify service-layer logic in isolation, and implement integration tests that validate API endpoints end-to-end. By the end, you'll run the full test suite and interpret results to confirm correct application behavior.
Lab Info
Table of Contents
-
Challenge
Set Up Your Testing Environment
Set Up Your Testing Environment
Welcome to the Book Management API testing lab!
The Scenario
You've just joined the team that maintains a Book Management API built with ASP.NET Core 10. The application works, but it has zero test coverage. Your first assignment: build an automated test suite so the team can ship with confidence.
The Architecture
The application follows a layered architecture:
Controllers -> Services -> Repository -> Data Store- Models —
Bookentity with properties: Id, Title, Author, ISBN, PublishedYear - Data —
IBookRepositoryinterface and an in-memoryBookRepositoryimplementation - Services —
IBookServiceinterface andBookServicewith business logic (validation, null checks) - Controllers —
BooksControllerexposing REST endpoints: GET all, GET by id, POST
Your Goal
You'll create a test project, write unit tests for the service layer, write integration tests for the API, run the full suite, and analyze the results.
What You'll Learn
- Unit testing — testing a single class in isolation using mock dependencies
- Integration testing — testing the full HTTP pipeline from request to response
- Test execution — running, filtering, and configuring test output
- Result analysis — reading test output and identifying gaps in coverage
Take a moment to read through
BookService.csandBooksController.cs— these are the classes you'll write tests against.info> If you get stuck, click the
Task Solutionlink at the bottom of any task to view the solution code. - Models —
-
Challenge
Write Unit Tests for the Service Layer
Now that the test project is set up, it's time to write your first tests.
What Are Unit Tests?
Unit tests verify the behavior of a single class or method in isolation. They test one "unit" of code at a time, without involving external dependencies like databases, file systems, or HTTP endpoints.
Why Test the Service Layer?
The
BookServiceclass contains business logic — validation rules, data transformations, error handling. This is exactly the kind of code that benefits from unit testing because:- It has clear inputs and outputs
- It depends on abstractions (
IBookRepository) that can be mocked - It contains business rules that should not break during refactoring
The Arrange-Act-Assert Pattern
Unit tests generally follow this structure:
- Arrange — Set up the test data and mock dependencies
- Act — Call the method under test
- Assert — Verify the expected outcome
What You'll Test
In this step, you'll write unit tests for three methods in
BookService:GetAllBooks()— returns all books from the repositoryGetBookById(int id)— returns a single book or nullAddBook(Book book)— validates and adds a new book
For each test, you'll mock
IBookRepositoryusing Moq to control what the repository returns and verify how the service interacts with it. -
Challenge
Write Integration Tests for the API Endpoints
Great work on the unit tests! Now let's move to integration testing.
Unit Tests vs. Integration Tests
Unit tests isolate a single class. Integration tests verify that multiple components work together correctly. For a web API, this means testing the full HTTP pipeline:
HTTP Request -> Routing -> Controller -> Service -> Repository -> HTTP ResponseWebApplicationFactory
ASP.NET Core provides
WebApplicationFactory<TEntryPoint>which spins up an in-memory test server. This lets you send real HTTP requests and inspect real responses — no external dependencies needed.public class BooksControllerTests : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _client; public BooksControllerTests(WebApplicationFactory<Program> factory) { _client = factory.CreateClient(); } }What You'll Test
GET /api/books— should return 200 OK with a JSON arrayPOST /api/books— should return 201 Created with the new book and a Location headerGET /api/books/{id}— should return 200 for valid IDs and 404 for non-existent ones
Each test sends a real HTTP request and asserts on the response status code, headers, and body.
-
Challenge
Run and Configure the Full Test Suite
You've written unit tests and integration tests. Now let's learn how to run, filter, and configure the test suite like a professional.
Running the Full Suite
So far you've been filtering tests to run specific subsets. To run everything:
dotnet run --project BookApi.Tests/BookApi.Tests.csprojThis runs all tests in the project. The xUnit v3 runner executes the test project as a console application.
Filtering Tests
xUnit v3 uses native filter flags passed after
--:# Run only unit tests dotnet run --project BookApi.Tests/BookApi.Tests.csproj -- -namespace "BookApi.Tests.UnitTests" # Run only integration tests dotnet run --project BookApi.Tests/BookApi.Tests.csproj -- -namespace "BookApi.Tests.IntegrationTests" # Run a specific test method (wildcards supported) dotnet run --project BookApi.Tests/BookApi.Tests.csproj -- -method "*AddBook*"Test Output Configuration
Control the verbosity of test output with reporter options:
# Default reporter (standard progress) dotnet run --project BookApi.Tests/BookApi.Tests.csproj # Verbose output with detailed timing dotnet run --project BookApi.Tests/BookApi.Tests.csproj -- -reporter verbose # Generate a TRX report for CI/CD systems dotnet run --project BookApi.Tests/BookApi.Tests.csproj -- -trx "TestResults/results.trx"CI Configuration
In continuous integration environments, you might want diagnostic tools:
# Generate a hang dump if a test hangs (useful for debugging) dotnet run --project BookApi.Tests/BookApi.Tests.csproj -- -hangdumpUnderstanding how to run and configure tests is essential for maintaining a healthy test suite in real projects.
-
Challenge
Analyze Test Results
You've built a test suite and learned how to run it. Now comes the real skill: diagnosing a failure, fixing a defect, and verifying the fix.
The Scenario
A teammate mentioned that the
GET /api/books/{id}endpoint might not handle missing books correctly. You notice the test suite has no test for a non-existent book ID — a gap in coverage.Diagnosing a Failure
You'll start by writing the missing test, observing the failure, and tracing it to the root cause in the controller.
Reading Test Output
When the test fails, the output tells you:
- Which test failed — the fully qualified test name
- What assertion failed — the specific
Assertcall that was not satisfied - What was expected vs. actual — for comparison assertions
- Stack trace — where in the test code the failure occurred
Understanding Failures
Rather than immediately fixing the code, take time to understand:
- What the test expected and what it received
- Which code path produced the incorrect result
- Whether the bug is in the test or the production code
Fix and Verify
After identifying the defect, apply the fix and re-run the entire suite to confirm:
- The failing test now passes
- No other tests were broken by the change
Identifying Coverage Gaps
In
BookService.AddBook(), we test null input and empty title. After Task 5.2, we also validate empty author. But what about:- Whitespace-only title (
" ") — doesIsNullOrWhiteSpacecatch it? - A book with the minimum valid fields only?
- A valid book with the longest possible title (200 characters)?
These are edge cases that thorough testers should consider. For integration tests, consider gaps like
POSTwith missing required fields (the controller returns400 BadRequestfor invalid models).Best Practices
- Read test output carefully before assuming a failure is a bug in the code
- Use verbose output to understand exactly which tests ran
- Regularly review test coverage to find gaps
- Write tests for edge cases, not just the happy path
- Always re-run the full suite after a fix to check for regressions
-
Challenge
Conclusion
Congratulations on completing this lab!
Throughout this lab, you've had a chance to experiment with some of the key features that allow you to thoroughly test your ASP.NET applicaitons including:
- Creating unit tests that isolate the tested code with mock objects.
- Self-contained integration tests that allow the full application stack to be exercised by automatically starting the application to be tested.
- Explored filtering and reporting capabilities to tune the test execution and output messages to match your needs.
- Learned how to use a test-first approach to confirm application errors before correcting them.
Feel free to continue to experiment in the lab environment to discover more about testing ASP.NET applications.
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.