|
| 1 | +--- |
| 2 | +title: Unit testing C# code in .NET using dotnet test and xUnit |
| 3 | +description: Learn unit test concepts in C# and .NET through an interactive experience building a sample solution step-by-step using dotnet test and xUnit. |
| 4 | +author: ardalis |
| 5 | +ms.author: wiwagn |
| 6 | +ms.date: 03/07/2024 |
| 7 | +--- |
| 8 | +# Unit testing C# in .NET using dotnet test and xUnit |
| 9 | + |
| 10 | +This tutorial shows how to build a solution containing a unit test project and source code project. To follow the tutorial using a pre-built solution, [view or download the sample code](https://github.com/dotnet/samples/tree/main/core/getting-started/unit-testing-using-dotnet-test/). For download instructions, see [Samples and Tutorials](../../samples-and-tutorials/index.md#view-and-download-samples). |
| 11 | + |
| 12 | +## Create the solution |
| 13 | + |
| 14 | +In this section, a solution is created that contains the source and test projects. The completed solution has the following directory structure: |
| 15 | + |
| 16 | +```txt |
| 17 | +/unit-testing-using-dotnet-test |
| 18 | + unit-testing-using-dotnet-test.sln |
| 19 | + /PrimeService |
| 20 | + PrimeService.cs |
| 21 | + PrimeService.csproj |
| 22 | + /PrimeService.Tests |
| 23 | + PrimeService_IsPrimeShould.cs |
| 24 | + PrimeServiceTests.csproj |
| 25 | +``` |
| 26 | + |
| 27 | +The following instructions provide the steps to create the test solution. See [Commands to create test solution](#create-test-cmd) for instructions to create the test solution in one step. |
| 28 | + |
| 29 | +* Open a shell window. |
| 30 | +* Run the following command: |
| 31 | + |
| 32 | + ```dotnetcli |
| 33 | + dotnet new sln -o unit-testing-using-dotnet-test |
| 34 | + ``` |
| 35 | + |
| 36 | + The [`dotnet new sln`](../tools/dotnet-new.md) command creates a new solution in the *unit-testing-using-dotnet-test* directory. |
| 37 | +* Change directory to the *unit-testing-using-dotnet-test* folder. |
| 38 | +* Run the following command: |
| 39 | + |
| 40 | + ```dotnetcli |
| 41 | + dotnet new classlib -o PrimeService |
| 42 | + ``` |
| 43 | + |
| 44 | + The [`dotnet new classlib`](../tools/dotnet-new.md) command creates a new class library project in the *PrimeService* folder. The new class library will contain the code to be tested. |
| 45 | +* Rename *Class1.cs* to *PrimeService.cs*. |
| 46 | +* Replace the code in *PrimeService.cs* with the following code: |
| 47 | + |
| 48 | + ```csharp |
| 49 | + using System; |
| 50 | + |
| 51 | + namespace Prime.Services |
| 52 | + { |
| 53 | + public class PrimeService |
| 54 | + { |
| 55 | + public bool IsPrime(int candidate) |
| 56 | + { |
| 57 | + throw new NotImplementedException("Not implemented."); |
| 58 | + } |
| 59 | + } |
| 60 | + } |
| 61 | + ``` |
| 62 | + |
| 63 | +* The preceding code: |
| 64 | + * Throws a <xref:System.NotImplementedException> with a message indicating it's not implemented. |
| 65 | + * Is updated later in the tutorial. |
| 66 | + |
| 67 | +<!-- preceding code shows an english bias. Message makes no sense outside english --> |
| 68 | + |
| 69 | +* In the *unit-testing-using-dotnet-test* directory, run the following command to add the class library project to the solution: |
| 70 | + |
| 71 | + ```dotnetcli |
| 72 | + dotnet sln add ./PrimeService/PrimeService.csproj |
| 73 | + ``` |
| 74 | + |
| 75 | +* Create the *PrimeService.Tests* project by running the following command: |
| 76 | + |
| 77 | + ```dotnetcli |
| 78 | + dotnet new xunit -o PrimeService.Tests |
| 79 | + ``` |
| 80 | + |
| 81 | +* The preceding command: |
| 82 | + * Creates the *PrimeService.Tests* project in the *PrimeService.Tests* directory. The test project uses [xUnit](https://xunit.net/) as the test library. |
| 83 | + * Configures the test runner by adding the following `<PackageReference />`elements to the project file: |
| 84 | + * `Microsoft.NET.Test.Sdk` |
| 85 | + * `xunit` |
| 86 | + * `xunit.runner.visualstudio` |
| 87 | + * `coverlet.collector` |
| 88 | + |
| 89 | +* Add the test project to the solution file by running the following command: |
| 90 | + |
| 91 | + ```dotnetcli |
| 92 | + dotnet sln add ./PrimeService.Tests/PrimeService.Tests.csproj |
| 93 | + ``` |
| 94 | + |
| 95 | +* Add the `PrimeService` class library as a dependency to the *PrimeService.Tests* project: |
| 96 | + |
| 97 | + ```dotnetcli |
| 98 | + dotnet add ./PrimeService.Tests/PrimeService.Tests.csproj reference ./PrimeService/PrimeService.csproj |
| 99 | + ``` |
| 100 | + |
| 101 | +<a name="create-test-cmd"></a> |
| 102 | + |
| 103 | +### Commands to create the solution |
| 104 | + |
| 105 | +This section summarizes all the commands in the previous section. Skip this section if you've completed the steps in the previous section. |
| 106 | + |
| 107 | +The following commands create the test solution on a Windows machine. For macOS and Unix, update the `ren` command to the OS version of `ren` to rename a file: |
| 108 | + |
| 109 | +```dotnetcli |
| 110 | +dotnet new sln -o unit-testing-using-dotnet-test |
| 111 | +cd unit-testing-using-dotnet-test |
| 112 | +dotnet new classlib -o PrimeService |
| 113 | +ren .\PrimeService\Class1.cs PrimeService.cs |
| 114 | +dotnet sln add ./PrimeService/PrimeService.csproj |
| 115 | +dotnet new xunit -o PrimeService.Tests |
| 116 | +dotnet add ./PrimeService.Tests/PrimeService.Tests.csproj reference ./PrimeService/PrimeService.csproj |
| 117 | +dotnet sln add ./PrimeService.Tests/PrimeService.Tests.csproj |
| 118 | +``` |
| 119 | + |
| 120 | +Follow the instructions for "Replace the code in *PrimeService.cs* with the following code" in the previous section. |
| 121 | + |
| 122 | +## Create a test |
| 123 | + |
| 124 | +A popular approach in test driven development (TDD) is to write a (failing) test before implementing the target code. This tutorial uses the TDD approach. The `IsPrime` method is callable, but not implemented. A test call to `IsPrime` fails. With TDD, a test is written that is known to fail. The target code is updated to make the test pass. You keep repeating this approach, writing a failing test and then updating the target code to pass. |
| 125 | + |
| 126 | +Update the *PrimeService.Tests* project: |
| 127 | + |
| 128 | +* Delete *PrimeService.Tests/UnitTest1.cs*. |
| 129 | +* Create a *PrimeService.Tests/PrimeService_IsPrimeShould.cs* file. |
| 130 | +* Replace the code in *PrimeService_IsPrimeShould.cs* with the following code: |
| 131 | + |
| 132 | +```csharp |
| 133 | +using Xunit; |
| 134 | +using Prime.Services; |
| 135 | + |
| 136 | +namespace Prime.UnitTests.Services |
| 137 | +{ |
| 138 | + public class PrimeService_IsPrimeShould |
| 139 | + { |
| 140 | + [Fact] |
| 141 | + public void IsPrime_InputIs1_ReturnFalse() |
| 142 | + { |
| 143 | + var primeService = new PrimeService(); |
| 144 | + bool result = primeService.IsPrime(1); |
| 145 | + |
| 146 | + Assert.False(result, "1 should not be prime"); |
| 147 | + } |
| 148 | + } |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +The `[Fact]` attribute declares a test method that's run by the test runner. From the *PrimeService.Tests* folder, run `dotnet test`. The [dotnet test](../tools/dotnet-test.md) command builds both projects and runs the tests. The xUnit test runner contains the program entry point to run the tests. `dotnet test` starts the test runner using the unit test project. |
| 153 | + |
| 154 | +The test fails because `IsPrime` hasn't been implemented. Using the TDD approach, write only enough code so this test passes. Update `IsPrime` with the following code: |
| 155 | + |
| 156 | +```csharp |
| 157 | +public bool IsPrime(int candidate) |
| 158 | +{ |
| 159 | + if (candidate == 1) |
| 160 | + { |
| 161 | + return false; |
| 162 | + } |
| 163 | + throw new NotImplementedException("Not fully implemented."); |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +Run `dotnet test`. The test passes. |
| 168 | + |
| 169 | +### Add more tests |
| 170 | + |
| 171 | +Add prime number tests for 0 and -1. You could copy the test created in the preceding step and make copies of the following code to test 0 and -1. |
| 172 | +But don't do it, as there's a better way. |
| 173 | + |
| 174 | +```csharp |
| 175 | +var primeService = new PrimeService(); |
| 176 | +bool result = primeService.IsPrime(1); |
| 177 | + |
| 178 | +Assert.False(result, "1 should not be prime"); |
| 179 | +``` |
| 180 | + |
| 181 | +Copying test code when only a parameter changes results in code duplication and test bloat. The following xUnit attributes enable writing a suite of similar tests: |
| 182 | + |
| 183 | +- `[Theory]` represents a suite of tests that execute the same code but have different input arguments. |
| 184 | +- `[InlineData]` attribute specifies values for those inputs. |
| 185 | + |
| 186 | +Rather than creating new tests, apply the preceding xUnit attributes to create a single theory. Replace the following code: |
| 187 | + |
| 188 | +```csharp |
| 189 | +[Fact] |
| 190 | +public void IsPrime_InputIs1_ReturnFalse() |
| 191 | +{ |
| 192 | + var primeService = new PrimeService(); |
| 193 | + bool result = primeService.IsPrime(1); |
| 194 | + |
| 195 | + Assert.False(result, "1 should not be prime"); |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +with the following code: |
| 200 | + |
| 201 | + :::code language="csharp" source="../../../samples/snippets/core/testing/unit-testing-using-dotnet-test/csharp/PrimeService.Tests/PrimeService_IsPrimeShould.cs" id="Sample_TestCode"::: |
| 202 | + |
| 203 | +In the preceding code, `[Theory]` and `[InlineData]` enable testing several values less than two. Two is the smallest prime number. |
| 204 | + |
| 205 | +Add the following code after the class declaration and before the `[Theory]` attribute: |
| 206 | + |
| 207 | + :::code language="csharp" source="../../../samples/snippets/core/testing/unit-testing-using-dotnet-test/csharp/PrimeService.Tests/PrimeService_IsPrimeShould.cs" id="Sample_InitCode"::: |
| 208 | + |
| 209 | +Run `dotnet test`, and two of the tests fail. To make all of the tests pass, update the `IsPrime` method with the following code: |
| 210 | + |
| 211 | +```csharp |
| 212 | +public bool IsPrime(int candidate) |
| 213 | +{ |
| 214 | + if (candidate < 2) |
| 215 | + { |
| 216 | + return false; |
| 217 | + } |
| 218 | + throw new NotImplementedException("Not fully implemented."); |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +Following the TDD approach, add more failing tests, then update the target code. See the [finished version of the tests](https://github.com/dotnet/samples/blob/main/core/getting-started/unit-testing-using-dotnet-test/PrimeService.Tests/PrimeService_IsPrimeShould.cs) and the [complete implementation of the library](https://github.com/dotnet/samples/blob/main/core/getting-started/unit-testing-using-dotnet-test/PrimeService/PrimeService.cs). |
| 223 | + |
| 224 | +The completed `IsPrime` method is not an efficient algorithm for testing primality. |
| 225 | + |
| 226 | +### Additional resources |
| 227 | + |
| 228 | +- [xUnit.net official site](https://xunit.net) |
| 229 | +- [Testing controller logic in ASP.NET Core](/aspnet/core/mvc/controllers/testing) |
| 230 | +- [`dotnet add reference`](../tools/dotnet-add-reference.md) |
0 commit comments