Unit testing Visual Basic .NET Core libraries using dotnet test and NUnit

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 before you begin. For download instructions, see Samples and Tutorials.

This article is about testing a .NET Core project. If you're testing an ASP.NET Core project, see Integration tests in ASP.NET Core.

Prerequisites

  • .NET 8 SDK or later versions.
  • A text editor or code editor of your choice.

Creating the source project

Open a shell window. Create a directory called unit-testing-vb-nunit to hold the solution. Inside this new directory, run the following command to create a new solution file for the class library and the test project:

dotnet new sln

Next, create a PrimeService directory. The following outline shows the file structure so far:

/unit-testing-vb-nunit
    unit-testing-vb-nunit.sln
    /PrimeService

Make PrimeService the current directory and run the following command to create the source project:

dotnet new classlib -lang VB

Rename Class1.VB to PrimeService.VB. You create a failing implementation of the PrimeService class:

Namespace Prime.Services
    Public Class PrimeService
        Public Function IsPrime(candidate As Integer) As Boolean
            Throw New NotImplementedException("Please create a test first.")
        End Function
    End Class
End Namespace

Change the directory back to the unit-testing-vb-using-mstest directory. Run the following command to add the class library project to the solution:

dotnet sln add .\PrimeService\PrimeService.vbproj

Creating the test project

Next, create the PrimeService.Tests directory. The following outline shows the directory structure:

/unit-testing-vb-nunit
    unit-testing-vb-nunit.sln
    /PrimeService
        Source Files
        PrimeService.vbproj
    /PrimeService.Tests

Make the PrimeService.Tests directory the current directory and create a new project using the following command:

dotnet new nunit -lang VB

The dotnet new command creates a test project that uses NUnit as the test library. The generated template configures the test runner in the PrimeServiceTests.vbproj file:

<ItemGroup>
  <PackageReference Include="nunit" Version="4.1.0" />
  <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
</ItemGroup>

Note

Prior to .NET 9, the generated code may reference older versions of the NUnit test framework. You may use dotnet CLI to update the packages. Alternatively, open the PrimeService.Tests.vbproj file and replace the contents of the package references item group with the code above.

The test project requires other packages to create and run unit tests. dotnet new in the previous step added NUnit and the NUnit test adapter. Now, add the PrimeService class library as another dependency to the project. Use the dotnet add reference command:

dotnet add reference ../PrimeService/PrimeService.vbproj

You can see the entire file in the samples repository on GitHub.

You have the following final solution layout:

/unit-testing-vb-nunit
    unit-testing-vb-nunit.sln
    /PrimeService
        Source Files
        PrimeService.vbproj
    /PrimeService.Tests
        Test Source Files
        PrimeService.Tests.vbproj

Execute the following command in the unit-testing-vb-nunit directory:

dotnet sln add .\PrimeService.Tests\PrimeService.Tests.vbproj

Creating the first test

You write one failing test, make it pass, then repeat the process. In the PrimeService.Tests directory, rename the UnitTest1.vb file to PrimeService_IsPrimeShould.VB and replace its entire contents with the following code:

Imports NUnit.Framework

Namespace PrimeService.Tests
    <TestFixture>
    Public Class PrimeService_IsPrimeShould
        Private _primeService As Prime.Services.PrimeService = New Prime.Services.PrimeService()

        <Test>
        Sub IsPrime_InputIs1_ReturnFalse()
            Dim result As Boolean = _primeService.IsPrime(1)

            Assert.That(result, [Is].False, $"1 should not be prime")
        End Sub

    End Class
End Namespace

The <TestFixture> attribute indicates a class that contains tests. The <Test> attribute denotes a method that is run by the test runner. From the unit-testing-vb-nunit, execute dotnet test to build the tests and the class library and then run the tests. The NUnit test runner contains the program entry point to run your tests. dotnet test starts the test runner using the unit test project you've created.

Your test fails. You haven't created the implementation yet. Make this test pass by writing the simplest code in the PrimeService class that works:

Public Function IsPrime(candidate As Integer) As Boolean
    If candidate = 1 Then
        Return False
    End If
    Throw New NotImplementedException("Please create a test first.")
End Function

In the unit-testing-vb-nunit directory, run dotnet test again. The dotnet test command runs a build for the PrimeService project and then for the PrimeService.Tests project. After building both projects, it runs this single test. It passes.

Adding more features

Now that you've made one test pass, it's time to write more. There are a few other simple cases for prime numbers: 0, -1. You could add those cases as new tests with the <Test> attribute, but that quickly becomes tedious. There are other xUnit attributes that enable you to write a suite of similar tests. A <TestCase> attribute represents a suite of tests that execute the same code but have different input arguments. You can use the <TestCase> attribute to specify values for those inputs.

Instead of creating new tests, apply these two attributes to create a series of tests that test several values less than two, which is the lowest prime number:

<TestFixture>
Public Class PrimeService_IsPrimeShould
    Private _primeService As Prime.Services.PrimeService = New Prime.Services.PrimeService()

    <TestCase(-1)>
    <TestCase(0)>
    <TestCase(1)>
    Sub IsPrime_ValuesLessThan2_ReturnFalse(value As Integer)
        Dim result As Boolean = _primeService.IsPrime(value)

        Assert.That(result, [Is].False, $"{value} should not be prime")
    End Sub

    <TestCase(2)>
    <TestCase(3)>
    <TestCase(5)>
    <TestCase(7)>
    Public Sub IsPrime_PrimesLessThan10_ReturnTrue(value As Integer)
        Dim result As Boolean = _primeService.IsPrime(value)

        Assert.That(result, [Is].True, $"{value} should be prime")
    End Sub

    <TestCase(4)>
    <TestCase(6)>
    <TestCase(8)>
    <TestCase(9)>
    Public Sub IsPrime_NonPrimesLessThan10_ReturnFalse(value As Integer)
        Dim result As Boolean = _primeService.IsPrime(value)

        Assert.That(result, [Is].False, $"{value} should not be prime")
    End Sub
End Class

Run dotnet test, and two of these tests fail. To make all of the tests pass, change the if clause at the beginning of the Main method in the PrimeServices.cs file:

if candidate < 2

Continue to iterate by adding more tests, more theories, and more code in the main library. You have the finished version of the tests and the complete implementation of the library.

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.