DEV Community

Cover image for A Beginner's Guide to Unit Testing in .NET with NUnit and Moq
Oussama Belhadi
Oussama Belhadi

Posted on

A Beginner's Guide to Unit Testing in .NET with NUnit and Moq

Unit testing is a crucial aspect of ensuring the correctness and reliability of your application. In this post, we'll go over how to set up NUnit for testing in a .NET project, including how to mock dependencies with Moq and test an authentication service.

Unit Testing Workflow:

Image description

Write the Test: Define a test method that checks a specific behavior or functionality.
Set Up Dependencies: Use mocking (e.g., Moq) to simulate external dependencies like databases or APIs.
Execute the Code: Call the method being tested with input data.
Assert the Output: Verify that the output matches the expected result.
Run

Setting Up NUnit in Your Project

Step 1: Install Necessary Packages

Before we can start writing unit tests, we need to install the required NuGet packages. You'll need NUnit for testing, Moq for mocking dependencies, and the NUnit Test Adapter for running tests in your IDE.

  1. Install NUnit (for the testing framework):
   dotnet add package NUnit
Enter fullscreen mode Exit fullscreen mode
  1. Install NUnit Test Adapter (to run tests in Visual Studio or with the .NET CLI):
   dotnet add package NUnit3TestAdapter
Enter fullscreen mode Exit fullscreen mode
  1. Install Moq (for mocking DbContext and other dependencies):
   dotnet add package Moq
Enter fullscreen mode Exit fullscreen mode
  1. Install Microsoft.NET.Test.Sdk (for running tests):
   dotnet add package Microsoft.NET.Test.Sdk
Enter fullscreen mode Exit fullscreen mode

After installing these packages, verify them in your .csproj file:

<ItemGroup>
  <PackageReference Include="Moq" Version="4.16.1" />
  <PackageReference Include="NUnit" Version="3.12.0" />
  <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Test Project

It's good practice to separate your tests from the main project. Follow these steps to create a new test project:

  1. Create a new test project:
   dotnet new nunit -n MyProject.Tests
   cd MyProject.Tests
Enter fullscreen mode Exit fullscreen mode
  1. Add reference to your main project:

Run this command from your test project folder:

   dotnet add reference ../MyProject
Enter fullscreen mode Exit fullscreen mode

Writing Unit Tests with NUnit

Now that we've set up NUnit, let's write some tests for the Authenticate method in the UserService.

Step 1: Write the Test Class

In your MyProject.Tests project, create a new test class to test the UserService.

using NUnit.Framework;
using Moq;
using backend.Models;
using backend.Services;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace MyProject.Tests
{
    public class UserServiceTests
    {
        private Mock<AppDbContext> _mockContext;
        private UserService _userService;
        private Mock<DbSet<User>> _mockUserSet;

        [SetUp]
        public void SetUp()
        {
            // Mock the AppDbContext and DbSet<User>
            _mockContext = new Mock<AppDbContext>();
            _mockUserSet = new Mock<DbSet<User>>();

            // Setup the mock context to return the mock DbSet
            _mockContext.Setup(c => c.Users).Returns(_mockUserSet.Object);

            // Initialize the UserService with the mocked context
            _userService = new UserService(_mockContext.Object);
        }

        [Test]
        public async Task Authenticate_ReturnsUser_WhenCredentialsAreCorrect()
        {
            // Arrange
            var user = new User
            {
                UserId = 1,
                UserName = "testuser",
                PasswordHash = BCrypt.Net.BCrypt.HashPassword("password")
            };

            // Setup the mock DbSet to return the user when querying for the username
            _mockUserSet.Setup(u => u.SingleOrDefaultAsync(It.IsAny<System.Linq.Expressions.Expression<System.Func<User, bool>>>(), default))
                        .ReturnsAsync(user);

            // Act
            var result = await _userService.Authenticate("testuser", "password");

            // Assert
            Assert.NotNull(result);
            Assert.AreEqual("testuser", result.UserName);
        }

        [Test]
        public async Task Authenticate_ReturnsNull_WhenCredentialsAreIncorrect()
        {
            // Arrange
            var user = new User
            {
                UserId = 1,
                UserName = "testuser",
                PasswordHash = BCrypt.Net.BCrypt.HashPassword("password")
            };

            // Setup the mock DbSet to return the user when querying for the username
            _mockUserSet.Setup(u => u.SingleOrDefaultAsync(It.IsAny<System.Linq.Expressions.Expression<System.Func<User, bool>>>(), default))
                        .ReturnsAsync(user);

            // Act
            var result = await _userService.Authenticate("testuser", "wrongpassword");

            // Assert
            Assert.Null(result);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • SetUp: The [SetUp] method is executed before each test. It sets up the mock DbContext and DbSet<User> to avoid direct database calls.
  • Test Methods: We have two test methods:
    • Authenticate_ReturnsUser_WhenCredentialsAreCorrect: Verifies that the Authenticate method returns the user when the correct credentials are provided.
    • Authenticate_ReturnsNull_WhenCredentialsAreIncorrect: Verifies that Authenticate returns null when the credentials are incorrect.
  • Moq Setup: Moq is used to simulate DbSet<User> and SingleOrDefaultAsync, so the tests don't depend on an actual database.

Step 2: Run Tests

Once your tests are written, you can run them using the .NET CLI or within Visual Studio.

  1. Run Tests with .NET CLI: In the terminal, execute:
   dotnet test
Enter fullscreen mode Exit fullscreen mode

This command will run all tests in your solution and show the results.

  1. Run Tests in Visual Studio: If you're using Visual Studio, the NUnit Test Adapter should display your tests in the "Test Explorer" panel. You can run them directly from there.

Step 3: Analyze Test Results

After running your tests, you'll get the results, indicating whether the tests passed or failed. If any test fails, the output will include an explanation of the error, which helps you fix issues in your code.


Best Practices for Writing Unit Tests

Here are some best practices for writing effective unit tests:

  1. Isolate Logic: Each test should only focus on one specific piece of functionality.
  2. Mock External Dependencies: Use Moq or similar libraries to simulate dependencies, such as DbContext or external services, so tests are isolated from the real systems.
  3. Test Edge Cases: Make sure to test for edge cases, such as empty inputs or invalid data.
  4. Use Clear Assertions: Use assertions like Assert.AreEqual, Assert.NotNull, and Assert.IsTrue to verify the expected behavior.

Running Tests in CI/CD Pipeline

Once your tests are ready, you can integrate them into your CI/CD pipeline to run automatically on each commit or build.

For example, in GitHub Actions, you can set up a YAML configuration file to run your tests:

name: .NET Core CI

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dotnet-version: [6.0]
    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Set up .NET SDK
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: ${{ matrix.dotnet-version }}

    - name: Restore dependencies
      run: dotnet restore

    - name: Build
      run: dotnet build --no-restore

    - name: Run Tests
      run: dotnet test --no-build --verbosity normal
Enter fullscreen mode Exit fullscreen mode

This ensures your tests run automatically whenever code is pushed or a pull request is made to the main branch.


Conclusion

By following this guide, you now know how to set up and write unit tests using NUnit and Moq in your .NET application. Unit tests are essential for ensuring the correctness of your code and improving the maintainability of your project. With NUnit, you can write clean and effective tests, and Moq allows you to mock dependencies for isolated testing.

Start writing tests for your application, and make sure to run them frequently to catch bugs early in your development cycle. Happy testing!

Top comments (0)