.NET + VS Code + XUnit - Setup Unit Testing & Code Coverage in ASP.NET Core
Tutorial built with .NET 7.0 and XUnit
Unit testing, it rarely makes the headlines but it's something we all have to do as developers.
I recently added unit testing and code coverage to a .NET 7.0 (ASP.NET Core) project in VS Code. This post covers all the steps I went through to set everything up.
The end result is a pair of .NET projects, one for the application being tested (an example API) and one for the unit tests, with VS Code configured to run the tests and generate code coverage reports at the click of a button.
Tutorial Contents
- Tools required for this tutorial
- Download example .NET API project
- Create unit test project with XUnit
- Add some unit tests
- Install VS Code test runner
- Run tests and view code coverage
Tools required for the the .NET unit testing tutorial
To develop and run .NET 7.0 applications locally, download and install the following:
- .NET SDK - includes the .NET runtime and command line tools
- Visual Studio Code - code editor that runs on Windows, Mac and Linux
- C# extension for Visual Studio Code - adds support to VS Code for developing .NET applications
Download example .NET API project to test
We'll start with an example .NET 7 CRUD API I posted recently that doesn't have any unit tests yet. The details of the API project aren't required for this tutorial, but if you're interested see .NET 7.0 + Dapper + SQLite - CRUD API Tutorial in ASP.NET Core.
- Create a new root folder for the solution named
dotnet-7-dapper-sqlite-crud-api
- Inside the new root folder, download or clone the .NET project from https://github.com/cornflourblue/dotnet-7-dapper-sqlite-crud-api into a folder named
WebAPI
, e.g. with the following git command:git clone https://github.com/cornflourblue/dotnet-7-dapper-sqlite-crud-api.git WebAPI
cd
into theWebAPI
folder and restore the project NuGet packages with the following dotnet command:dotnet restore
Command output
The git clone
output should look something like this:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api> git clone https://github.com/cornflourblue/dotnet-7-dapper-sqlite-crud-api.git WebAPI
Cloning into 'WebAPI'...
remote: Enumerating objects: 26, done.
remote: Counting objects: 100% (26/26), done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 26 (delta 1), reused 26 (delta 1), pack-reused 0
Unpacking objects: 100% (26/26), 7.36 KiB | 27.00 KiB/s, done.
And this is the output from dotnet restore
:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI> dotnet restore
Determining projects to restore...
Restored C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI\WebApi.csproj (in 428 ms).
Create .NET unit test project with XUnit
These steps create a new unit test project and add a reference to the project we want to test.
- Inside the root folder (
/dotnet-7-dapper-sqlite-crud-api
), create a new folder for the unit test project namedWebAPI.Tests
- From the command line,
cd
into the/dotnet-7-dapper-sqlite-crud-api/WebAPI.Tests
folder and run the following command to create the unit test project with XUnit:dotnet new xunit
- From the same folder run the following command to add a reference to the WebAPI project from the unit test project:
dotnet add reference ..\WebAPI\WebApi.csproj
- Delete the auto generated test file
UnitTest1.cs
Command output and screenshots
The dotnet new xunit
output should look something like this:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests> dotnet new xunit
The template "xUnit Test Project" was created successfully.
Processing post-creation actions...
Restoring C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj:
Determining projects to restore...
Restored C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj (in 1.56 sec).
Restore succeeded.
This is the dotnet add reference
output:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests> dotnet add reference ..\WebAPI\WebApi.csproj
Reference `..\WebAPI\WebApi.csproj` added to the project.
And this is how our projects should look in the VS Code file explorer now:
Add some .NET unit tests
Here we install a couple of NuGet packages (Moq and AutoFixture) that help with setting up test data and mocking dependencies, and add a few initial unit tests for the UserService
class in the example API project.
- Install the AutoFixture package from NuGet with the following command:
dotnet add package AutoFixture
- Install the Moq package from NuGet with the following command:
dotnet add package Moq
- Create a new folder named
Services
in theWebAPI.Tests
project - Create a new file named
UserServiceTests.cs
in the Services folder with the following tests:using AutoFixture; using AutoMapper; using Moq; using WebApi.Entities; using WebApi.Helpers; using WebApi.Repositories; using WebApi.Services; namespace WebApi.Tests.Services; public class UserServiceTests { private IUserService _userService; private Mock<IUserRepository> _mockUserRepository; private Fixture _fixture; public UserServiceTests() { // fixture for creating test data _fixture = new Fixture(); // mock user repo dependency _mockUserRepository = new Mock<IUserRepository>(); // automapper dependency var mapper = new MapperConfiguration(x => x.AddProfile<AutoMapperProfile>()).CreateMapper(); // service under test _userService = new UserService(_mockUserRepository.Object, mapper); } [Fact] public async Task GetAll_ReturnsAllUsers() { // Arrange var usersFixture = _fixture.CreateMany<User>(3); _mockUserRepository.Setup(x => x.GetAll()).ReturnsAsync(usersFixture); // Act var users = await _userService.GetAll(); // Assert Assert.True(users.Count() == 3); Assert.Equal(usersFixture.First().FirstName, users.First().FirstName); } [Fact] public async Task GetById_ValidId_ReturnsUser() { // Arrange var userFixture = _fixture.Create<User>(); var id = userFixture.Id; _mockUserRepository.Setup(x => x.GetById(id)).ReturnsAsync(userFixture); // Act var user = await _userService.GetById(id); // Assert Assert.NotNull(user); Assert.Equal(userFixture.FirstName, user.FirstName); } [Fact] public async Task GetById_InvalidId_ThrowsException() { // Act & Assert await Assert.ThrowsAsync<KeyNotFoundException>(() => _userService.GetById(100)); } }
Command output and screenshots
Output from dotnet add package AutoFixture
:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests> dotnet add package AutoFixture
Determining projects to restore...
Writing C:\Users\Jason\AppData\Local\Temp\tmp564F.tmp
info : X.509 certificate chain validation will use the default trust store selected by .NET.
info : X.509 certificate chain validation will use the default trust store selected by .NET.
info : Adding PackageReference for package 'AutoFixture' into project 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : GET https://api.nuget.org/v3/registration5-gz-semver2/autofixture/index.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/autofixture/index.json 300ms
info : GET https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/2.0.0/3.6.2.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/2.0.0/3.6.2.json 945ms
info : GET https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/3.6.3/3.24.6.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/3.6.3/3.24.6.json 1004ms
info : GET https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/3.25.0/3.47.0.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/3.25.0/3.47.0.json 229ms
info : GET https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/3.47.1/4.18.0.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/autofixture/page/3.47.1/4.18.0.json 1018ms
info : Restoring packages for C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj...
info : Package 'AutoFixture' is compatible with all the specified frameworks in project 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : PackageReference for package 'AutoFixture' version '4.18.0' added to file 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : Generating MSBuild file C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\obj\WebAPI.Tests.csproj.nuget.g.targets.
info : Writing assets file to disk. Path: C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\obj\project.assets.json
log : Restored C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj (in 449 ms).
Output from dotnet add package Moq
:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests> dotnet add package Moq
Determining projects to restore...
Writing C:\Users\Jason\AppData\Local\Temp\tmpEF06.tmp
info : X.509 certificate chain validation will use the default trust store selected by .NET.
info : X.509 certificate chain validation will use the default trust store selected by .NET.
info : Adding PackageReference for package 'Moq' into project 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : GET https://api.nuget.org/v3/registration5-gz-semver2/moq/index.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/moq/index.json 625ms
info : Restoring packages for C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj...
info : Package 'Moq' is compatible with all the specified frameworks in project 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : PackageReference for package 'Moq' version '4.18.4' added to file 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : Writing assets file to disk. Path: C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\obj\project.assets.json
log : Restored C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj (in 334 ms).
VS Code with UserServiceTests.cs
:
Install VS Code test runner
The following steps are to install a test runner VS Code extension and a NuGet package for collecting code coverage data.
- Install the VS Code extension .NET Core Test Explorer, we'll be using this to run our tests in VS Code.
- In the
/WebAPI.Tests
project, install the Coverlet MSBuild package from NuGet with the following command:dotnet add package coverlet.msbuild
- Create or update the
/.vscode/settings.json
file with the following settings to configure the test explorer extension and coverlet:{ "dotnet-test-explorer.autoExpandTree": true, "dotnet-test-explorer.autoWatch": true, "dotnet-test-explorer.treeMode": "full", "dotnet-test-explorer.testArguments": "/p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./TestResults/lcov.info", "dotnet-test-explorer.testProjectPath": "**/*.Tests.csproj" }
- Select the new VS Code Testing tab to view the three unit tests in our project, click the refresh icon if the tests aren't visible yet.
Command output and screenshots
VS Code after installing the .NET Core Test Explorer extension:
Output from dotnet add package coverlet.msbuild
:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests> dotnet add package coverlet.msbuild
Determining projects to restore...
Writing C:\Users\Jason\AppData\Local\Temp\tmp691.tmp
info : X.509 certificate chain validation will use the default trust store selected by .NET.
info : X.509 certificate chain validation will use the default trust store selected by .NET.
info : Adding PackageReference for package 'coverlet.msbuild' into project 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : GET https://api.nuget.org/v3/registration5-gz-semver2/coverlet.msbuild/index.json
info : OK https://api.nuget.org/v3/registration5-gz-semver2/coverlet.msbuild/index.json 1130ms
info : Restoring packages for C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj...
info : Package 'coverlet.msbuild' is compatible with all the specified frameworks in project 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : PackageReference for package 'coverlet.msbuild' version '3.2.0' added to file 'C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj'.
info : Generating MSBuild file C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\obj\WebAPI.Tests.csproj.nuget.g.props.
info : Generating MSBuild file C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\obj\WebAPI.Tests.csproj.nuget.g.targets.
info : Writing assets file to disk. Path: C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\obj\project.assets.json
log : Restored C:\Projects\dotnet-7-dapper-sqlite-crud-api\WebAPI.Tests\WebAPI.Tests.csproj (in 377 ms).
VS Code after creating the /.vscode/settings.json
file:
VS Code with the new Testing tab selected on the left menu:
Run tests and view code coverage
Now we'll run the tests and view the code coverage information a couple of different ways, first with the Coverage Gutters VS Code extension that highlights covered/uncovered lines within the VS Code editor pane, then we'll generate a code coverage HTML report for the whole project using the ReportGenerator dotnet tool.
- Run the tests by clicking the play button in the VS Code Testing tab. After they pass there should be a generated code coverage file at
/WebAPI.Tests/TestResults/lcov.info
, the file contains coverage data and is not meant to be directly human readable. - Install the VS Code extension Coverage Gutters, it displays coverage info from the lcov file in the VS Code gutter to the left of the code.
- Open the
WebAPI/Services/UserService.cs
class and enable Coverage Gutters by clicking Watch in the VS Code status bar. The gutter next to the line numbers should be green for covered lines and red for uncovered lines, and the status bar should show the percentage that the file is covered. - Finally let's generate an HTML coverage report for the whole project using the ReportGenerator dotnet tool. Install the ReportGenerator tool globally with the following command:
dotnet tool install -g dotnet-reportgenerator-globaltool
- Create or update the
/.vscode/tasks.json
file with the following task to generate an HTML coverage report based on the lcov.info file:{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "generate coverage report", "command": "reportgenerator", "type": "shell", "args": [ "-reports:${workspaceFolder}/WebApi.Tests/TestResults/lcov.info", "-targetdir:${workspaceFolder}/WebApi.Tests/TestResults/coveragereport" ], "problemMatcher": [] } ] }
- Run the task by selecting: Terminal > Run Task... > generate coverage report. Or with the shortcut: ctrl+shift+p > Tasks: Run Task > generate coverage report.
- In VS Code expand the folder
WebAPI.Tests/TestResults/coveragereport
, right click theindex.htm
file and select Show Preview. The coverage report will be displayed in the main panel.
That's it!
We're all setup to run unit tests and generate code coverage reports quickly and easily with VS Code for ASP.NET Core projects.
Command output and screenshots
The VS Code Testing tab after the tests are successfully run:
VS Code showing the generated lcov.info
file with code coverage data, this file is used to generate coverage reports and is not intended to be directly human readable:
VS Code after installing the Coverage Gutters extension:
The UserService.cs
class displaying coverage information with the coverage gutters extension:
Output from dotnet tool install -g dotnet-reportgenerator-globaltool
:
PS C:\Projects\dotnet-7-dapper-sqlite-crud-api> dotnet tool install -g dotnet-reportgenerator-globaltool
You can invoke the tool using the following command: reportgenerator
Tool 'dotnet-reportgenerator-globaltool' (version '5.1.19') was successfully installed.
VS Code after creating the /.vscode/tasks.json
file:
VS Code terminal output after running the report generator task:
* Executing task: reportgenerator -reports:C:\Projects\dotnet-7-dapper-sqlite-crud-api/WebApi.Tests/TestResults/lcov.info -targetdir:C:\Projects\dotnet-7-dapper-sqlite-crud-api/WebApi.Tests/TestResults/coveragereport
2023-03-14T14:28:14: Arguments
2023-03-14T14:28:14: -reports:C:\Projects\dotnet-7-dapper-sqlite-crud-api/WebApi.Tests/TestResults/lcov.info
2023-03-14T14:28:14: -targetdir:C:\Projects\dotnet-7-dapper-sqlite-crud-api/WebApi.Tests/TestResults/coveragereport
2023-03-14T14:28:14: Writing report file 'C:\Projects\dotnet-7-dapper-sqlite-crud-api/WebApi.Tests/TestResults/coveragereport\index.html'
* Terminal will be reused by tasks, press any key to close it.
Preview of generated HTML code coverage report in VS Code:
Need Some VS Code Help?
Search fiverr for freelance VS Code developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!