diff --git a/docs/core/testing/index.md b/docs/core/testing/index.md index ce1cc9db5ccea..590cc98b03415 100644 --- a/docs/core/testing/index.md +++ b/docs/core/testing/index.md @@ -74,7 +74,14 @@ For more information, see the following resources: #### TUnit -[TUnit](https://thomhurst.github.io/TUnit/) is entirely built on top of Microsoft.Testing.Platform and doesn't support VSTest. For more information, refer to TUnit documentation. +[TUnit](https://tunit.dev/) is a testing framework for .NET that is built entirely on top of Microsoft.Testing.Platform and doesn't support VSTest. TUnit uses source generation for C# test discovery and is Native AOT compatible. Tests run in parallel by default. + +For more information, see the following resources: + +- [TUnit official documentation](https://tunit.dev/) +- [Unit testing with C#](unit-testing-csharp-with-tunit.md) +- [Unit testing with F#](unit-testing-fsharp-with-tunit.md) +- [Unit testing with Visual Basic](unit-testing-visual-basic-with-tunit.md) #### xUnit.net diff --git a/docs/core/testing/microsoft-testing-platform-intro.md b/docs/core/testing/microsoft-testing-platform-intro.md index 21f5e558f600a..6a7e958a7f69d 100644 --- a/docs/core/testing/microsoft-testing-platform-intro.md +++ b/docs/core/testing/microsoft-testing-platform-intro.md @@ -41,7 +41,7 @@ The main driving factors for the evolution of the new testing platform are detai * MSTest. In MSTest, the support of `Microsoft.Testing.Platform` is done via [MSTest runner](unit-testing-mstest-runner-intro.md). * NUnit. In NUnit, the support of `Microsoft.Testing.Platform` is done via [NUnit runner](unit-testing-nunit-runner-intro.md). * xUnit.net: In xUnit.net, the support of `Microsoft.Testing.Platform` is done via [xUnit.net runner](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform). -* TUnit: entirely constructed on top of the `Microsoft.Testing.Platform`, for more information, see [TUnit documentation](https://tunit.dev/). +* TUnit: Built entirely on Microsoft.Testing.Platform and doesn't support VSTest. Unlike MSTest, NUnit, and xUnit which support both VSTest and MTP, TUnit only supports MTP. For more information, see [TUnit documentation](https://tunit.dev/) and [Getting started with TUnit](unit-testing-csharp-with-tunit.md). ## Run and debug tests diff --git a/docs/core/testing/mutation-testing.md b/docs/core/testing/mutation-testing.md index 14be11aaf2fec..afb16e276d7ad 100644 --- a/docs/core/testing/mutation-testing.md +++ b/docs/core/testing/mutation-testing.md @@ -169,4 +169,46 @@ public void InvalidPrice_ShouldThrowExceptionWithCorrectMessage() :::image type="content" source="media/stryker-final-report.png" lightbox="media/stryker-final-report.png" alt-text="Stryker final report"::: +## Using mutation testing with TUnit + +Mutation testing with Stryker.NET works with TUnit. The examples above can be written using TUnit's async testing syntax: + +```csharp +[Test] +public async Task ApplyDiscountCorrectly() +{ + decimal price = 100; + decimal discountPercent = 10; + + var calculator = new PriceCalculator(); + + var result = calculator.CalculatePrice(price, discountPercent); + + await Assert.That(result).IsEqualTo(90.00m); +} + +[Test] +public async Task InvalidDiscountPercent_ShouldThrowException() +{ + var calculator = new PriceCalculator(); + + await Assert.That(() => calculator.CalculatePrice(100, -1)) + .Throws(); + await Assert.That(() => calculator.CalculatePrice(100, 101)) + .Throws(); +} + +[Test] +public async Task InvalidPrice_ShouldThrowExceptionWithCorrectMessage() +{ + var calculator = new PriceCalculator(); + + await Assert.That(() => calculator.CalculatePrice(0, 10)) + .Throws() + .WithMessage("Price must be greater than zero."); +} +``` + +For more information about TUnit, see [Unit testing C# with TUnit](unit-testing-csharp-with-tunit.md). + Mutation testing helps to find opportunities to improve tests that make them more reliable. It forces you to check not only the 'happy path', but also complex boundary cases, reducing the likelihood of bugs in production. diff --git a/docs/core/testing/order-unit-tests.md b/docs/core/testing/order-unit-tests.md index 64b934dc803d8..1a994d4e6b3d9 100644 --- a/docs/core/testing/order-unit-tests.md +++ b/docs/core/testing/order-unit-tests.md @@ -92,6 +92,37 @@ To order tests explicitly, NUnit provides an [`OrderAttribute`](https://docs.nun :::code language="csharp" source="snippets/order-unit-tests/csharp/NUnit.TestProject/ByOrder.cs"::: +:::zone-end +:::zone pivot="tunit" + +## Order by dependency + +TUnit provides a `[DependsOn]` attribute to control test execution order through explicit dependencies. When a test depends on another, TUnit ensures the prerequisite test completes before executing the dependent test. This approach allows you to maintain test parallelism for independent tests while enforcing order where necessary. + +:::code language="csharp" source="snippets/order-unit-tests/csharp/TUnit.TestProject/ByDependency.cs"::: + +### Behavior + +* **Failure handling** - By default, if a dependency fails, the dependent test is skipped. You can override this behavior by setting `ProceedOnFailure = true` on the `DependsOnAttribute`. +* **Accessing dependent test context** - You can retrieve data from a prerequisite test's context using the `GetTests` method on the `TestContext` object. +* **Multiple dependencies** - You can apply multiple `[DependsOn]` attributes to a single test to create complex dependency chains. + +### Handling method overloads + +When depending on overloaded test methods, specify the parameter types explicitly: + +```csharp +[Test] +[DependsOn(nameof(SetupTest), [typeof(string), typeof(int)])] +public async Task DependentTest() +{ + // This test runs after SetupTest(string, int) completes +} +``` + +> [!NOTE] +> While `[DependsOn]` provides ordering capabilities, tests should ideally be self-contained, isolated, and side-effect free. Reserve dependency ordering for scenarios where independent tests are impractical, such as deployment pipelines or expensive multi-step workflows. + :::zone-end ## Next Steps diff --git a/docs/core/testing/selective-unit-tests.md b/docs/core/testing/selective-unit-tests.md index aa0d6436e3942..2971207b7acbc 100644 --- a/docs/core/testing/selective-unit-tests.md +++ b/docs/core/testing/selective-unit-tests.md @@ -31,7 +31,8 @@ dotnet test --filter | -------------- | -------------------- | | MSTest | `FullyQualifiedName`
`Name`
`ClassName`
`Priority`
`TestCategory` | | xUnit | `FullyQualifiedName`
`DisplayName`
`Traits` | - | Nunit | `FullyQualifiedName`
`Name`
`Priority`
`TestCategory` | + | NUnit | `FullyQualifiedName`
`Name`
`Priority`
`TestCategory` | + | TUnit | `FullyQualifiedName`
`Name`
`Category`
`Property` | * **Operators** @@ -230,6 +231,63 @@ dotnet test --filter "(FullyQualifiedName~UnitTest1&TestCategory=CategoryA)|Prio For more information, see [TestCase filter](https://github.com/Microsoft/vstest-docs/blob/main/docs/filter.md). +:::zone-end +:::zone pivot="tunit" + +## TUnit examples + +```csharp +using TUnit.Core; + +namespace TUnitNamespace +{ + public class UnitTest1 + { + [Test, Property("Priority", "1"), Category("CategoryA")] + public async Task TestMethod1() + { + } + + [Test, Property("Priority", "2")] + public async Task TestMethod2() + { + } + } +} +``` + +TUnit uses the `--treenode-filter` flag with a path-based syntax: + +| Expression | Result | +|--|--| +| `dotnet test --treenode-filter "/*/*/*/*Method*"` | Runs tests whose method name contains `Method`. | +| `dotnet test --treenode-filter "/*/*/*/TestMethod1"` | Runs tests whose name is `TestMethod1`. | +| `dotnet test --treenode-filter "/*/TUnitNamespace/UnitTest1/*"` | Runs all tests in class `TUnitNamespace.UnitTest1`. | +| `dotnet test --treenode-filter "/**[Category=CategoryA]"` | Runs tests that are annotated with `[Category("CategoryA")]`. | +| `dotnet test --treenode-filter "/**[Priority=2]"` | Runs tests that have `[Property("Priority", "2")]`. | + +In the code example, the `[Property]` and `[Category]` attributes can be used for filtering. + +Examples using the conditional operators `|` and `&`: + +To run tests that have `UnitTest1` in their class name **or** have a `Category` of `"CategoryA"`. + +```dotnetcli +dotnet test --treenode-filter "(/*/*/UnitTest1/*)|/**[Category=CategoryA]" +``` + +To run tests that are in class `UnitTest1` **and** have a `Category` of `"CategoryA"`. + +```dotnetcli +dotnet test --treenode-filter "(/*/*/UnitTest1/*)&/**[Category=CategoryA]" +``` + +To run tests that have either class `UnitTest1` **and** `Category` of `"CategoryA"` **or** have a `Property` with `"Priority"` of `"2"`. + +```dotnetcli +dotnet test --treenode-filter "((/*/*/UnitTest1/*)&/**[Category=CategoryA])|/**[Priority=2]" +``` + :::zone-end ## See also diff --git a/docs/core/testing/unit-testing-code-coverage.md b/docs/core/testing/unit-testing-code-coverage.md index 824bc1281bd84..ef5e3caffbb67 100644 --- a/docs/core/testing/unit-testing-code-coverage.md +++ b/docs/core/testing/unit-testing-code-coverage.md @@ -295,6 +295,93 @@ After running this command, an HTML file represents the generated report. :::image type="content" source="media/test-report.png" lightbox="media/test-report.png" alt-text="Unit test-generated report"::: +## Using code coverage with TUnit + +TUnit is built on Microsoft.Testing.Platform and uses Microsoft.Testing.Extensions.CodeCoverage for code coverage. + +### Creating a TUnit test project with code coverage + +To create a TUnit test project with code coverage support: + +```dotnetcli +dotnet new install TUnit.Templates +dotnet new tunit -n TUnit.CodeCoverage.Test +``` + +Add the project reference to your class library: + +```dotnetcli +dotnet add TUnit.CodeCoverage.Test\TUnit.CodeCoverage.Test.csproj reference Numbers\Numbers.csproj +``` + +Add the Microsoft.Testing.Extensions.CodeCoverage package: + +```dotnetcli +dotnet add TUnit.CodeCoverage.Test package Microsoft.Testing.Extensions.CodeCoverage +``` + +### TUnit test example + +```csharp +using TUnit.Assertions; +using TUnit.Assertions.Extensions; +using TUnit.Core; +using System.Numbers; + +public class PrimeServiceTests +{ + [Test] + public async Task IsPrime_InputIs1_ReturnFalse() + { + var primeService = new PrimeService(); + + bool result = primeService.IsPrime(1); + + await Assert.That(result).IsFalse(); + } + + [Test] + [Arguments(2)] + [Arguments(3)] + [Arguments(5)] + [Arguments(7)] + public async Task IsPrime_PrimesLessThan10_ReturnTrue(int value) + { + var primeService = new PrimeService(); + + bool result = primeService.IsPrime(value); + + await Assert.That(result).IsTrue(); + } +} +``` + +### Running code coverage with TUnit + +Since TUnit requires Microsoft.Testing.Platform mode, ensure your `global.json` includes: + +```json +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} +``` + +Run tests with code coverage: + +```dotnetcli +dotnet test --coverage +``` + +This generates a `.coverage` file in the `TestResults` directory. To generate reports in other formats: + +```dotnetcli +dotnet test --coverage --coverage-output-format cobertura +``` + +For more information about TUnit, see [Unit testing C# with TUnit](unit-testing-csharp-with-tunit.md). + ## See also - [Visual Studio unit test code coverage](/visualstudio/test/using-code-coverage-to-determine-how-much-code-is-being-tested) diff --git a/docs/core/testing/unit-testing-csharp-with-tunit.md b/docs/core/testing/unit-testing-csharp-with-tunit.md new file mode 100644 index 0000000000000..9927a8dbeb2d1 --- /dev/null +++ b/docs/core/testing/unit-testing-csharp-with-tunit.md @@ -0,0 +1,253 @@ +--- +title: Unit testing C# code in .NET using dotnet test and TUnit +description: Learn unit test concepts in C# and .NET through an interactive experience building a sample solution step-by-step using dotnet test and TUnit. +author: thomhurst +ms.author: wiwagn +ms.date: 11/22/2025 +ai-usage: ai-assisted +--- +# Unit testing C# in .NET using dotnet test and TUnit + +This tutorial shows how to build a solution containing a unit test project and source code project. To follow the tutorial using a prebuilt 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). + +## Prerequisites + +TUnit is built entirely on [Microsoft.Testing.Platform](microsoft-testing-platform-intro.md). Unlike frameworks that support both VSTest and Microsoft.Testing.Platform, TUnit only supports Microsoft.Testing.Platform. + +## Create the solution + +In this section, a solution is created that contains the source and test projects. The completed solution has the following directory structure: + +```txt +/unit-testing-using-dotnet-test + unit-testing-using-dotnet-test.sln + /PrimeService + PrimeService.cs + PrimeService.csproj + /PrimeService.Tests + PrimeService_IsPrimeShould.cs + PrimeServiceTests.csproj +``` + +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. + +* Open a shell window. +* Run the following command: + + ```dotnetcli + dotnet new sln -o unit-testing-using-dotnet-test + ``` + + The [`dotnet new sln`](../tools/dotnet-new.md) command creates a new solution in the *unit-testing-using-dotnet-test* directory. +* Change directory to the *unit-testing-using-dotnet-test* folder. +* Run the following command: + + ```dotnetcli + dotnet new classlib -o PrimeService + ``` + + 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. +* Rename *Class1.cs* to *PrimeService.cs*. +* Replace the code in *PrimeService.cs* with the following code: + + ```csharp + using System; + + namespace Prime.Services + { + public class PrimeService + { + public bool IsPrime(int candidate) + { + throw new NotImplementedException("Not implemented."); + } + } + } + ``` + + Currently this code throws a , but you'll implement the method later in the tutorial. + +* In the *unit-testing-using-dotnet-test* directory, run the following command to add the class library project to the solution: + + ```dotnetcli + dotnet sln add ./PrimeService/PrimeService.csproj + ``` + +* Install the TUnit project template: + + ```dotnetcli + dotnet new install TUnit.Templates + ``` + +* Create the *PrimeService.Tests* project by running the following command: + + ```dotnetcli + dotnet new tunit -o PrimeService.Tests + ``` + + The preceding command creates the *PrimeService.Tests* project in the *PrimeService.Tests* directory. The test project uses [TUnit](https://tunit.dev/) as the test library. TUnit uses source generation for test discovery. The template configures the test runner by adding the `TUnit` package to the project file. + +* Add the test project to the solution file by running the following command: + + ```dotnetcli + dotnet sln add ./PrimeService.Tests/PrimeService.Tests.csproj + ``` + +* Add the `PrimeService` class library as a dependency to the *PrimeService.Tests* project: + + ```dotnetcli + dotnet add ./PrimeService.Tests/PrimeService.Tests.csproj reference ./PrimeService/PrimeService.csproj + ``` + + + +### Commands to create the solution + +This section summarizes all the commands in the previous section. Skip this section if you've completed the steps in the previous section. + +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: + +```dotnetcli +dotnet new sln -o unit-testing-using-dotnet-test +cd unit-testing-using-dotnet-test +dotnet new classlib -o PrimeService +ren .\PrimeService\Class1.cs PrimeService.cs +dotnet sln add ./PrimeService/PrimeService.csproj +dotnet new install TUnit.Templates +dotnet new tunit -o PrimeService.Tests +dotnet add ./PrimeService.Tests/PrimeService.Tests.csproj reference ./PrimeService/PrimeService.csproj +dotnet sln add ./PrimeService.Tests/PrimeService.Tests.csproj +``` + +Follow the instructions for "Replace the code in *PrimeService.cs* with the following code" in the previous section. + +## Configure Microsoft.Testing.Platform mode + +TUnit only supports Microsoft.Testing.Platform and doesn't support VSTest. To use `dotnet test` with TUnit, add the following configuration to your `global.json` file in the solution root: + +```json +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} +``` + +This configuration enables the Microsoft.Testing.Platform mode of `dotnet test`. For more information, see [Testing with dotnet test](unit-testing-with-dotnet-test.md). + +## Create a test + +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, you write a test that's known to fail. Then you update the target code to make the test pass. You keep repeating this approach, writing a failing test and then updating the target code to pass. + +Update the *PrimeService.Tests* project: + +* Delete *PrimeService.Tests/UnitTest1.cs*. +* Create a *PrimeService.Tests/PrimeService_IsPrimeShould.cs* file. +* Replace the code in *PrimeService_IsPrimeShould.cs* with the following code: + + ```csharp + using TUnit.Assertions; + using TUnit.Assertions.Extensions; + using Prime.Services; + + namespace Prime.UnitTests.Services; + + public class PrimeService_IsPrimeShould + { + [Test] + public async Task IsPrime_InputIs1_ReturnFalse() + { + var primeService = new PrimeService(); + bool result = primeService.IsPrime(1); + + await Assert.That(result).IsFalse(); + } + } + ``` + +The `[Test]` attribute marks a method as a test that's run by the test runner. TUnit uses source generation to discover tests at compile time. + +From the *PrimeService.Tests* folder, run `dotnet test`. The [dotnet test](../tools/dotnet-test.md) command builds both projects and runs the tests. The TUnit test runner uses Microsoft.Testing.Platform to execute the tests. + +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: + +```csharp +public bool IsPrime(int candidate) +{ + if (candidate == 1) + { + return false; + } + throw new NotImplementedException("Not fully implemented."); +} +``` + +Run `dotnet test`. The test passes. + +### Add more tests + +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. But don't do it, as there's a better way. + +```csharp +var primeService = new PrimeService(); +bool result = primeService.IsPrime(1); + +await Assert.That(result).IsFalse(); +``` + +Copying test code when only a parameter changes results in code duplication and test bloat. TUnit provides the `[Arguments]` attribute to specify different input values for the same test logic: + +Rather than creating new tests, apply the `[Arguments]` attribute to create parameterized tests. Replace the following code... + +```csharp +[Test] +public async Task IsPrime_InputIs1_ReturnFalse() +{ + var primeService = new PrimeService(); + bool result = primeService.IsPrime(1); + + await Assert.That(result).IsFalse(); +} +``` + +...with the following code: + +```csharp +[Test] +[Arguments(-1)] +[Arguments(0)] +[Arguments(1)] +public async Task IsPrime_ValuesLessThan2_ReturnFalse(int value) +{ + var primeService = new PrimeService(); + bool result = primeService.IsPrime(value); + + await Assert.That(result).IsFalse(); +} +``` + +In the preceding code, `[Arguments]` enables testing several values less than two. Two is the smallest prime number. Each `[Arguments]` attribute generates a separate test case, and TUnit executes these tests in parallel by default. + +Run `dotnet test`, and two of the tests fail. To make all of the tests pass, update the `IsPrime` method with the following code: + +```csharp +public bool IsPrime(int candidate) +{ + if (candidate < 2) + { + return false; + } + throw new NotImplementedException("Not fully implemented."); +} +``` + +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). + +The completed `IsPrime` method isn't an efficient algorithm for testing primality. + +### Additional resources + +* [TUnit official site](https://tunit.dev) +* [TUnit GitHub repository](https://github.com/thomhurst/TUnit) +* [Testing controller logic in ASP.NET Core](/aspnet/core/mvc/controllers/testing) +* [`dotnet reference add`](../tools/dotnet-reference-add.md) diff --git a/docs/core/testing/unit-testing-fsharp-with-tunit.md b/docs/core/testing/unit-testing-fsharp-with-tunit.md new file mode 100644 index 0000000000000..a18c4a8171fa4 --- /dev/null +++ b/docs/core/testing/unit-testing-fsharp-with-tunit.md @@ -0,0 +1,208 @@ +--- +title: Unit testing F# in .NET Core with dotnet test and TUnit +description: Learn unit test concepts for F# in .NET Core through an interactive experience building a sample solution step-by-step using dotnet test and TUnit. +author: thomhurst +ms.author: wiwagn +ms.date: 11/22/2025 +ai-usage: ai-assisted +--- +# Unit testing F# libraries in .NET Core using dotnet test and TUnit + +This tutorial takes you through an interactive experience building a sample solution step-by-step to learn unit testing concepts. If you prefer 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-with-fsharp/) before you begin. For download instructions, see [Samples and Tutorials](../../samples-and-tutorials/index.md#view-and-download-samples). + +[!INCLUDE [testing an ASP.NET Core project from .NET Core](../../../includes/core-testing-note-aspnet.md)] + +## Prerequisites + +TUnit is built entirely on [Microsoft.Testing.Platform](microsoft-testing-platform-intro.md). Unlike frameworks that support both VSTest and Microsoft.Testing.Platform, TUnit only supports Microsoft.Testing.Platform. + +## Creating the source project + +Open a shell window. Create a directory called *unit-testing-with-fsharp* to hold the solution. +Inside this new directory, run `dotnet new sln` to create a new solution. This +makes it easier to manage both the class library and the unit test project. +Inside the solution directory, create a *MathService* directory. The directory and file structure thus far is shown below: + +``` +/unit-testing-with-fsharp + unit-testing-with-fsharp.sln + /MathService +``` + +Make *MathService* the current directory, and run `dotnet new classlib -lang "F#"` to create the source project. You'll create a failing implementation of the math service: + +```fsharp +module MyMath = + let squaresOfOdds xs = raise (System.NotImplementedException("You haven't written a test yet!")) +``` + +Change the directory back to the *unit-testing-with-fsharp* directory. Run `dotnet sln add .\MathService\MathService.fsproj` to add the class library project to the solution. + +## Creating the test project + +Next, create the *MathService.Tests* directory. The following outline shows the directory structure: + +``` +/unit-testing-with-fsharp + unit-testing-with-fsharp.sln + /MathService + Source Files + MathService.fsproj + /MathService.Tests +``` + +Install the TUnit project template: + +```dotnetcli +dotnet new install TUnit.Templates +``` + +Make the *MathService.Tests* directory the current directory and create a new project using `dotnet new tunit -lang "F#"`. This creates a test project that uses TUnit as the test library. The generated template configures the test runner by adding the `TUnit` package to the *MathServiceTests.fsproj* file. + +The test project requires other packages to create and run unit tests. `dotnet new` in the previous step added TUnit. Now, add the `MathService` class library as another dependency to the project. Use the `dotnet reference add` command: + +```dotnetcli +dotnet reference add ../MathService/MathService.fsproj +``` + +> [!TIP] +> If you're using .NET 9 SDK or earlier, use the "verb first" form (`dotnet add reference`) instead. The "noun first" form was introduced in .NET 10. + +You can see the entire file in the [samples repository](https://github.com/dotnet/samples/blob/main/core/getting-started/unit-testing-with-fsharp/MathService.Tests/MathService.Tests.fsproj) on GitHub. + +You have the following final solution layout: + +``` +/unit-testing-with-fsharp + unit-testing-with-fsharp.sln + /MathService + Source Files + MathService.fsproj + /MathService.Tests + Test Source Files + MathServiceTests.fsproj +``` + +Execute `dotnet sln add .\MathService.Tests\MathService.Tests.fsproj` in the *unit-testing-with-fsharp* directory. + +## Configure Microsoft.Testing.Platform mode + +TUnit only supports Microsoft.Testing.Platform and doesn't support VSTest. To use `dotnet test` with TUnit, add the following configuration to your `global.json` file in the solution root: + +```json +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} +``` + +This configuration enables the Microsoft.Testing.Platform mode of `dotnet test`. For more information, see [Testing with dotnet test](unit-testing-with-dotnet-test.md). + +## Creating the first test + +You write one failing test, make it pass, then repeat the process. Open *Tests.fs* and add the following code: + +```fsharp +open TUnit.Assertions +open TUnit.Assertions.Extensions +open TUnit.Core + +[] +let ``My test`` () = + task { + do! Assert.That(true).IsTrue() + } + +[] +let ``Fail every time`` () = + task { + do! Assert.That(false).IsTrue() + } +``` + +The `[]` attribute marks a method as a test that's run by the test runner. + +From the *unit-testing-with-fsharp* directory, execute `dotnet test` to build the tests and the class library and then run the tests. The TUnit test runner uses Microsoft.Testing.Platform to execute the tests. + +These two tests show the most basic passing and failing tests. `My test` passes, and `Fail every time` fails. Now, create a test for the `squaresOfOdds` method. The `squaresOfOdds` method returns a sequence of the squares of all odd integer values that are part of the input sequence. Rather than trying to write all of those functions at once, you can iteratively create tests that validate the functionality. Making each test pass means creating the necessary functionality for the method. + +The simplest test we can write is to call `squaresOfOdds` with all even numbers, where the result should be an empty sequence of integers. Here's that test: + +```fsharp +[] +let ``Sequence of Evens returns empty collection`` () = + task { + let expected = Seq.empty + let actual = MyMath.squaresOfOdds [2; 4; 6; 8; 10] + do! Assert.That(actual).IsEquivalentTo(expected) + } +``` + +Your test fails. You haven't created the implementation yet. Make this test pass by writing the simplest code in the `MathService` class that works: + +```fsharp +let squaresOfOdds xs = + Seq.empty +``` + +In the *unit-testing-with-fsharp* directory, run `dotnet test` again. The `dotnet test` command runs a build for the `MathService` project and then for the `MathService.Tests` project. After building both projects, it runs this single test. It passes. + +## Completing the requirements + +Now that you've made one test pass, it's time to write more. The next simple case works with a sequence whose only odd number is `1`. The number 1 is easier because the square of 1 is 1. Here's that next test: + +```fsharp +[] +let ``Sequences of Ones and Evens returns Ones`` () = + task { + let expected = [1; 1; 1; 1] + let actual = MyMath.squaresOfOdds [2; 1; 4; 1; 6; 1; 8; 1; 10] + do! Assert.That(actual).IsEquivalentTo(expected) + } +``` + +Executing `dotnet test` runs your tests and shows you that the new test fails. Now, update the `squaresOfOdds` method to handle this new test. You filter all the even numbers out of the sequence to make this test pass. You can do that by writing a small filter function and using `Seq.filter`: + +```fsharp +let private isOdd x = x % 2 <> 0 + +let squaresOfOdds xs = + xs + |> Seq.filter isOdd +``` + +There's one more step to go: square each of the odd numbers. Start by writing a new test: + +```fsharp +[] +let ``SquaresOfOdds works`` () = + task { + let expected = [1; 9; 25; 49; 81] + let actual = MyMath.squaresOfOdds [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] + do! Assert.That(actual).IsEquivalentTo(expected) + } +``` + +You can fix the test by piping the filtered sequence through a map operation to compute the square of each odd number: + +```fsharp +let private square x = x * x +let private isOdd x = x % 2 <> 0 + +let squaresOfOdds xs = + xs + |> Seq.filter isOdd + |> Seq.map square +``` + +You've built a small library and a set of unit tests for that library. You've structured the solution so that adding new packages and tests is part of the normal workflow. You've concentrated most of your time and effort on solving the goals of the application. + +## See also + +- [TUnit official site](https://tunit.dev) +- [TUnit GitHub repository](https://github.com/thomhurst/TUnit) +- [dotnet new](../tools/dotnet-new.md) +- [dotnet sln](../tools/dotnet-sln.md) +- [dotnet reference add](../tools/dotnet-reference-add.md) +- [dotnet test](../tools/dotnet-test.md) diff --git a/docs/core/testing/unit-testing-visual-basic-with-tunit.md b/docs/core/testing/unit-testing-visual-basic-with-tunit.md new file mode 100644 index 0000000000000..68dc5803e5ffb --- /dev/null +++ b/docs/core/testing/unit-testing-visual-basic-with-tunit.md @@ -0,0 +1,254 @@ +--- +title: Unit testing Visual Basic in .NET Core with dotnet test and TUnit +description: Learn unit test concepts in .NET Core through an interactive experience building a sample Visual Basic solution step-by-step using dotnet test and TUnit. +author: thomhurst +ms.author: wiwagn +ms.date: 11/22/2025 +ai-usage: ai-assisted +--- +# Unit testing Visual Basic .NET Core libraries using dotnet test and TUnit + +This tutorial shows how to build a solution containing a unit test project and library 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). + +## Prerequisites + +TUnit is built entirely on [Microsoft.Testing.Platform](microsoft-testing-platform-intro.md). Unlike frameworks that support both VSTest and Microsoft.Testing.Platform, TUnit only supports Microsoft.Testing.Platform. + +## Create the solution + +In this section, a solution is created that contains the source and test projects. The completed solution has the following directory structure: + +``` +/unit-testing-using-dotnet-test + unit-testing-using-dotnet-test.sln + /PrimeService + PrimeService.vb + PrimeService.vbproj + /PrimeService.Tests + PrimeService_IsPrimeShould.vb + PrimeServiceTests.vbproj +``` + +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. + +* Open a shell window. +* Run the following command: + + ```dotnetcli + dotnet new sln -o unit-testing-using-dotnet-test + ``` + + The [`dotnet new sln`](../tools/dotnet-new.md) command creates a new solution in the *unit-testing-using-dotnet-test* directory. +* Change directory to the *unit-testing-using-dotnet-test* folder. +* Run the following command: + + ```dotnetcli + dotnet new classlib -o PrimeService -lang VB + ``` + + 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. +* Rename *Class1.vb* to *PrimeService.vb*. +* Replace the code in *PrimeService.vb* with the following code: + + ```vb + Imports System + + Namespace Prime.Services + Public Class PrimeService + Public Function IsPrime(candidate As Integer) As Boolean + Throw New NotImplementedException("Not implemented.") + End Function + End Class + End Namespace + ``` + +* The preceding code: + * Throws a with a message indicating it's not implemented. + * Is updated later in the tutorial. + + + +* In the *unit-testing-using-dotnet-test* directory, run the following command to add the class library project to the solution: + + ```dotnetcli + dotnet sln add ./PrimeService/PrimeService.vbproj + ``` + +* Install the TUnit project template: + + ```dotnetcli + dotnet new install TUnit.Templates + ``` + +* Create the *PrimeService.Tests* project by running the following command: + + ```dotnetcli + dotnet new tunit -o PrimeService.Tests -lang VB + ``` + +* The preceding command: + * Creates the *PrimeService.Tests* project in the *PrimeService.Tests* directory. The test project uses [TUnit](https://tunit.dev/) as the test library. + * Configures the test runner by adding the `TUnit` package to the project file. + +* Add the test project to the solution file by running the following command: + + ```dotnetcli + dotnet sln add ./PrimeService.Tests/PrimeService.Tests.vbproj + ``` + +* Add the `PrimeService` class library as a dependency to the *PrimeService.Tests* project: + + ```dotnetcli + dotnet add ./PrimeService.Tests/PrimeService.Tests.vbproj reference ./PrimeService/PrimeService.vbproj + ``` + + + +### Commands to create the solution + +This section summarizes all the commands in the previous section. Skip this section if you've completed the steps in the previous section. + +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: + +```dotnetcli +dotnet new sln -o unit-testing-using-dotnet-test +cd unit-testing-using-dotnet-test +dotnet new classlib -o PrimeService -lang VB +ren .\PrimeService\Class1.vb PrimeService.vb +dotnet sln add ./PrimeService/PrimeService.vbproj +dotnet new install TUnit.Templates +dotnet new tunit -o PrimeService.Tests -lang VB +dotnet add ./PrimeService.Tests/PrimeService.Tests.vbproj reference ./PrimeService/PrimeService.vbproj +dotnet sln add ./PrimeService.Tests/PrimeService.Tests.vbproj +``` + +Follow the instructions for "Replace the code in *PrimeService.vb* with the following code" in the previous section. + +## Configure Microsoft.Testing.Platform mode + +TUnit only supports Microsoft.Testing.Platform and doesn't support VSTest. To use `dotnet test` with TUnit, add the following configuration to your `global.json` file in the solution root: + +```json +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} +``` + +This configuration enables the Microsoft.Testing.Platform mode of `dotnet test`. For more information, see [Testing with dotnet test](unit-testing-with-dotnet-test.md). + +## Create a test + +A popular approach in test driven development (TDD) is to write a 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. + +Update the *PrimeService.Tests* project: + +* Delete *PrimeService.Tests/UnitTest1.vb*. +* Create a *PrimeService.Tests/PrimeService_IsPrimeShould.vb* file. +* Replace the code in *PrimeService_IsPrimeShould.vb* with the following code: + +```vb +Imports TUnit.Assertions +Imports TUnit.Assertions.Extensions +Imports TUnit.Core + +Namespace PrimeService.Tests + Public Class PrimeService_IsPrimeShould + Private ReadOnly _primeService As Prime.Services.PrimeService + + Public Sub New() + _primeService = New Prime.Services.PrimeService() + End Sub + + + Function IsPrime_InputIs1_ReturnFalse() As Task + Return Task.Run(Async Function() + Dim result As Boolean = _primeService.IsPrime(1) + Await Assert.That(result).IsFalse() + End Function) + End Function + + End Class +End Namespace +``` + +The `[Test]` attribute marks a method as a test 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 TUnit test runner uses Microsoft.Testing.Platform to execute the tests. + +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: + +```vb +Public Function IsPrime(candidate As Integer) As Boolean + If candidate = 1 Then + Return False + End If + Throw New NotImplementedException("Not implemented.") +End Function +``` + +Run `dotnet test`. The test passes. + +### Add more tests + +Add prime number tests for 0 and -1. You could copy the preceding test and change the following code to use 0 and -1: + +```vb +Dim result As Boolean = _primeService.IsPrime(1) + +Await Assert.That(result).IsFalse() +``` + +Copying test code when only a parameter changes results in code duplication and test bloat. TUnit provides the `[Arguments]` attribute to specify different input values for the same test logic: + +Rather than creating new tests, apply the `[Arguments]` attribute to create parameterized tests. Replace the following code: + +```vb + +Function IsPrime_InputIs1_ReturnFalse() As Task + Return Task.Run(Async Function() + Dim result As Boolean = _primeService.IsPrime(1) + Await Assert.That(result).IsFalse() + End Function) +End Function +``` + +with the following code: + +```vb + + + + +Function IsPrime_ValuesLessThan2_ReturnFalse(ByVal value As Integer) As Task + Return Task.Run(Async Function() + Dim result As Boolean = _primeService.IsPrime(value) + Await Assert.That(result).IsFalse() + End Function) +End Function +``` + +In the preceding code, `[Arguments]` enables testing several values less than two. Two is the smallest prime number. Each `[Arguments]` attribute generates a separate test case, and TUnit executes these tests in parallel by default. + +Run `dotnet test`, two of the tests fail. To make all of the tests pass, update the `IsPrime` method with the following code: + +```vb +Public Function IsPrime(candidate As Integer) As Boolean + If candidate < 2 Then + Return False + End If + Throw New NotImplementedException("Not fully implemented.") +End Function +``` + +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-vb-dotnet-test/PrimeService.Tests/PrimeService_IsPrimeShould.vb) and the [complete implementation of the library](https://github.com/dotnet/samples/blob/main/core/getting-started/unit-testing-vb-dotnet-test/PrimeService/PrimeService.vb). + +The completed `IsPrime` method is not an efficient algorithm for testing primality. + +### Additional resources + +- [TUnit official site](https://tunit.dev) +- [TUnit GitHub repository](https://github.com/thomhurst/TUnit) +- [Testing controller logic in ASP.NET Core](/aspnet/core/mvc/controllers/testing) +- [`dotnet reference add`](../tools/dotnet-reference-add.md)