Chapter 35: Testing applications with xUnit
This introduction frames the chapter as a practical guide to creating and running isolated xUnit unit tests in .NET before extending those basics to ASP.NET Core testing.
After this chapter you will be able to
- xUnit unit-test workflow. Describe the chapter's practical xUnit workflow: create a test project, reference classes from another project, run tests through Visual Studio or the .NET CLI, and write tests for a currency-converter service's expected results and exceptions.
- Testing progression and scope. State that this chapter focuses on isolated unit tests for service behavior and the mechanics of .NET testing, while chapter 36 builds on those basics with ASP.NET Core unit tests and integration tests that exercise multiple components together.
- Test execution tooling. Explain how testing is integrated into the .NET SDK, including that dotnet test runs a test project's tests regardless of the chosen test framework and produces the same results as Visual Studio's test runner.
- Unit and integration tests. Distinguish unit tests from integration tests: unit tests run a small piece of code in isolation and verify its expected behavior, whereas integration tests verify interactions among multiple components or larger vertical slices of an app.
- xUnit test project creation. Describe how to create an xUnit test project using the Visual Studio xUnit Test Project template or an equivalent .NET CLI template, and distinguish this choice from MSTest or NUnit templates.
- xUnit Fact test structure. Describe the structural requirements of an xUnit Fact test: it is a method on a public, nonstatic class, marked with [Fact], public, and parameterless; it returns void or, when asynchronous, Task.
- dotnet test workflow. Explain how to run tests from the command line with dotnet test and what the command does. State that running it in a unit-test project folder restores and builds that project before executing its tests, while running it from a solution folder runs all test projects referenced by the solution.
- Test project references. Describe how a test project gains access to classes in the web application project by adding a project reference, either through Visual Studio or by editing the test project's .csproj file.
- Currency conversion behavior. Describe the sample CurrencyConverter class as having a ConvertToGbp() method that converts an amount to GBP using the supplied exchange rate, rounds the result to the requested number of decimal places, and returns it.
- Arrange Act Assert structure. Explain the Arrange, Act, Assert (AAA) structure of the Fact test: arrange inputs and the system under test, act by calling the method and capturing its result, and assert that the result equals the expected value. State that xUnit's Assert.Equal() fails the test by throwing an exception when actual and expected values differ.
- Fact and Theory parameterization. Distinguish a Fact test for one specified case from a Theory test that accepts parameters and runs repeatedly for supplied data sets. Describe how multiple [InlineData] attributes provide arguments for separate Theory runs, avoiding copied test methods, and note that each data set appears as a separate test.
- Edge-case input behavior. Distinguish valid edge cases from invalid inputs when testing a method such as CurrencyConverter.ConvertToGbp. Explain that a negative amount can be a valid case with a negative converted result, whereas a zero or negative exchange rate is conceptually invalid.
- Expected exception assertions. Describe how xUnit's Assert.Throws<T> tests an expected exception: it runs a lambda representing the action, catches the exception, and passes only when the thrown exception matches the expected type. State that the returned exception can also be inspected with further assertions, such as checking its message.
Section by section
Introduction
This introduction frames the chapter as a practical guide to creating and running isolated xUnit unit tests in .NET before extending those basics to ASP.NET Core testing.
- Automated testing purpose: Explain why automated tests become valuable as projects grow: they run scenarios automatically instead of requiring manual application testing. Distinguish this practical benefit from broader debates about test quantity, timing, and code-coverage targets, which the chapter does not address.
Why it matters: Automated tests give you a repeatable way to check behavior without manually exercising every scenario. - xUnit unit-test workflow: Describe the chapter's practical xUnit workflow: create a test project, reference classes from another project, run tests through Visual Studio or the .NET CLI, and write tests for a currency-converter service's expected results and exceptions.
Why it matters: This workflow lets you verify a service's behavior in isolation with executable tests. - Testing progression and scope: State that this chapter focuses on isolated unit tests for service behavior and the mechanics of .NET testing, while chapter 36 builds on those basics with ASP.NET Core unit tests and integration tests that exercise multiple components together.
Why it matters: Knowing the boundary helps you understand why this chapter starts with small isolated tests before testing a whole web application.
Key terms: automated testing, manual testing, code coverage, xUnit, unit test project, isolated unit tests, .NET command-line interface (CLI), Visual Studio, currency-converter service, services
35.1 An introduction to testing in ASP.NET Core
Introduces ASP.NET Core testing, distinguishing unit and integration tests and showing how .NET tooling and framework design support both.
- Test execution tooling: Explain how testing is integrated into the .NET SDK, including that dotnet test runs a test project's tests regardless of the chosen test framework and produces the same results as Visual Studio's test runner.
Why it matters: You can run automated tests consistently from either the command line or Visual Studio. - Test project components: Describe a test project as a console app containing test methods and identify its usual dependencies: the .NET Test SDK, a unit-testing framework, and that framework's test-runner adapter.
Why it matters: These packages connect ordinary test code to dotnet test and the Visual Studio test runner. - Unit and integration tests: Distinguish unit tests from integration tests: unit tests run a small piece of code in isolation and verify its expected behavior, whereas integration tests verify interactions among multiple components or larger vertical slices of an app.
Why it matters: Using both kinds of tests checks not only that individual components work but also that they work together. - ASP.NET Core testability: Explain why ASP.NET Core components are amenable to unit testing—its design avoids static types, favors interfaces over concrete implementations, and is modular—while recognizing that integration tests can exercise more of the app using an in-process Test Host and can isolate database concerns with EF Core's SQLite in-memory provider.
Why it matters: The framework's design and test infrastructure let you choose isolation for focused checks or more realistic component interaction without external-server and database setup burdens.
Key terms: .NET SDK, dotnet test, Visual Studio test runner, test project, .NET Test SDK, unit testing framework, test-runner adapter, NuGet packages, unit tests, integration tests
35.2 Creating your first test project with xUnit
This section shows how to create an xUnit test project and explains the project setup and structural requirements of its default Fact test.
- xUnit test project creation: Describe how to create an xUnit test project using the Visual Studio xUnit Test Project template or an equivalent .NET CLI template, and distinguish this choice from MSTest or NUnit templates.
Why it matters: Creating a test project provides the separate project and test framework needed to begin writing and running automated tests. - Test project package setup: Explain that the test-project template creates a console project and adds the required testing NuGet packages, including Microsoft.NET.Test.Sdk, which marks the project as a test project by setting the IsTestProject MSBuild property.
Why it matters: The SDK and framework packages let build and test tooling recognize the project and discover its tests. - xUnit Fact test structure: Describe the structural requirements of an xUnit Fact test: it is a method on a public, nonstatic class, marked with [Fact], public, and parameterless; it returns void or, when asynchronous, Task.
Why it matters: These conventions allow xUnit to identify and execute a method as a Fact test. - Test project entry point: Explain why an xUnit test project has no visible Program class or static Main method even though it is a console project: the test SDK injects Program at build time, so adding your own Program.cs can create multiple entry points.
Why it matters: This explains the unusual project shape and helps avoid an entry-point build error.
Key terms: xUnit, xUnit Test Project, MSTest Project, NUnit Test Project, Visual Studio, .NET CLI, NuGet packages, Microsoft.NET.Test.Sdk, IsTestProject, MSBuild
35.3 Running tests with dotnet test
This section shows how to run xUnit tests in Visual Studio or with the dotnet test CLI command and interpret their results.
- Visual Studio test execution: Describe how to run all tests in Visual Studio and what the Test Explorer displays. State that tests can be run through Test > Run All Tests or Run All in Test Explorer, which lists discovered tests and their latest pass/fail results.
Why it matters: Visual Studio provides a quick graphical way to discover, run, and review tests while developing. - xUnit passing condition: State how xUnit determines that a test passed: the test passes when it completes without throwing an exception. Distinguish this from a test that throws an exception, which is reported as a failure.
Why it matters: Knowing that exceptions determine xUnit failure helps you understand the pass/fail result reported by a test runner. - dotnet test workflow: Explain how to run tests from the command line with dotnet test and what the command does. State that running it in a unit-test project folder restores and builds that project before executing its tests, while running it from a solution folder runs all test projects referenced by the solution.
Why it matters: The dotnet test command lets you run the same tests without relying on a particular IDE.
Key terms: Test Explorer, Test > Run All Tests, Run All, xUnit, exception, pass, dotnet test, .NET CLI, .NET Test SDK, solution folder
35.4 Referencing your app from your test project
This section connects an xUnit test project to an ASP.NET Core app project, describes common solution layouts, and introduces the CurrencyConverter class that subsequent tests will exercise.
- Test project references: Describe how a test project gains access to classes in the web application project by adding a project reference, either through Visual Studio or by editing the test project's .csproj file.
Why it matters: A test project must reference the application project before its tests can instantiate and test application classes. - Relative project paths: Explain that a ProjectReference specifies the relative path to the referenced project's .csproj file, and that .. moves up to a parent directory in that path.
Why it matters: Understanding relative paths lets you add and interpret project references when editing project files directly. - Conventional solution layout: Describe the optional ASP.NET Core solution-layout conventions: placing the solution file at the root, main projects under src, tests under test or tests, and naming a corresponding test project with a .Test or .Tests suffix.
Why it matters: Recognizing these conventions makes it easier to navigate ASP.NET Core and open-source projects. - Currency conversion behavior: Describe the sample CurrencyConverter class as having a ConvertToGbp() method that converts an amount to GBP using the supplied exchange rate, rounds the result to the requested number of decimal places, and returns it.
Why it matters: This small, deterministic class supplies the behavior that the following unit tests will verify.
Key terms: project reference, Dependencies, Reference Manager, ProjectReference, .csproj, ItemGroup, relative path, parent folder, solution file, src
35.5 Adding Fact and Theory unit tests
This section shows how to structure xUnit unit tests for typical scenarios and parameterize repeated test cases with theories.
- Unit test path categories: Describe the happy path, error path, and edge cases as three categories of inputs or scenarios to consider when unit testing a method. Explain that the section tests ConvertToGbp() with typical happy-path inputs and includes zero as an edge case.
Why it matters: Considering normal, invalid, and boundary inputs helps tests cover more than a single successful example. - Arrange Act Assert structure: Explain the Arrange, Act, Assert (AAA) structure of the Fact test: arrange inputs and the system under test, act by calling the method and capturing its result, and assert that the result equals the expected value. State that xUnit's Assert.Equal() fails the test by throwing an exception when actual and expected values differ.
Why it matters: AAA makes a test's setup, behavior, and expected outcome clear to both the test runner and future readers. - Fact and Theory parameterization: Distinguish a Fact test for one specified case from a Theory test that accepts parameters and runs repeatedly for supplied data sets. Describe how multiple [InlineData] attributes provide arguments for separate Theory runs, avoiding copied test methods, and note that each data set appears as a separate test.
Why it matters: Theories let you cover multiple input combinations without duplicating the same test logic.
Key terms: happy path, error path, edge cases, ConvertToGbp(), Arrange, Act, Assert, AAA, system under test, SUT, Assert.Equal(), Fact
35.6 Testing failure conditions
This section shows how to test valid edge cases and invalid inputs in xUnit, including verifying expected exceptions without making brittle implementation-dependent tests.
- Edge-case input behavior: Distinguish valid edge cases from invalid inputs when testing a method such as CurrencyConverter.ConvertToGbp. Explain that a negative amount can be a valid case with a negative converted result, whereas a zero or negative exchange rate is conceptually invalid.
Why it matters: Tests should confirm that an application behaves correctly at its boundaries, not only for ordinary inputs. - Expected exception assertions: Describe how xUnit's Assert.Throws<T> tests an expected exception: it runs a lambda representing the action, catches the exception, and passes only when the thrown exception matches the expected type. State that the returned exception can also be inspected with further assertions, such as checking its message.
Why it matters: Exception assertions let tests verify that invalid inputs fail in the intended way. - Implementation-independent tests: Explain why tests should verify observable behavior rather than being tied too closely to a method's internal implementation. State that implementation-coupled tests become brittle, so trivial class changes can break tests even when behavior remains correct.
Why it matters: Behavior-focused tests remain useful when code is safely refactored.
Key terms: edge cases, valid cases, invalid values, CurrencyConverter, ConvertToGbp, Assert.Throws, ArgumentException, lambda function, Fact, Assert
35.7 Summary
This summary recaps how to create, run, and write xUnit tests, including data-driven tests and assertions for expected outcomes or failures.
Where this is used later
- Integration testing: Testing your whole app in-memory (chapter 36) · Integration tests extend automated checking from isolated code to multiple application components.
- Unit testing custom middleware (chapter 36) · Uses the unit-testing foundation for custom middleware.
- Unit testing API controllers and minimal API endpoints (chapter 36) · Uses the unit-testing foundation for API controllers and minimal API endpoints.
- Isolating the database with an in-memory EF Core provider (chapter 36) · The purpose of testing ASP.NET Core applications
- Creating a TestServer using the Test Host package (chapter 36) · This section creates an in-process TestServer with the Test Host package.
- (chapter intro) (chapter 36) · Creating an xUnit test project.
- Testing your application with WebApplicationFactory (chapter 36) · Referencing the application project from the test project.
- Reducing duplication by creating a custom WebApplicationFactory (chapter 36) · A custom WebApplicationFactory is introduced to reduce duplication while testing application behavior.
Concepts explained in this chapter
- testA test is an automated program method that evaluates whether application behavior matches an expected result.
- test automation frameworkA testing framework is a software framework that provides conventions, APIs, and execution support for automated tests.
- testing SDK.NET Test SDK is the .NET SDK component that provides the test-running infrastructure and integration required by .NET test projects.
- test projectA unit test app is a .NET console application project that contains automated tests and their test dependencies.
- FactFact is an xUnit attribute identifying a parameterless method as an individual test case.
- assertionAn assertion is a test operation that verifies an actual result satisfies an expected condition.
- dotnet testdotnet test is a .NET CLI command that restores and builds a test project and then runs its tests.
- test-runner adapterA test runner adapter is a component that enables a test runner to discover and execute tests written for a particular test framework.
- InlineDataInlineData is an xUnit attribute that supplies literal argument values for an individual theory test case.
- TheoryTheory is an xUnit attribute that marks a parameterized test method executed once for each supplied data set.
- Microsoft.AspNetCore.TestHostMicrosoft.AspNetCore.TestHost is an ASP.NET Core library that provides infrastructure for hosting and testing applications in memory.
- database providerA database provider model is an abstraction that allows an object-relational mapper to target different database systems through provider-specific implementations.
- integration testAn integration test evaluates the interaction of multiple application components or a substantial vertical slice of an application.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
