Mastering Unit Testing in ASP.NET Core Web API

Jeslur Rahman
10 min readJan 19, 2024

--

Testing is a cornerstone of the software development life cycle, ensuring that applications stand tall in terms of reliability, stability, and maintainability. Robust testing practices are not just desirable; they are the bedrock of delivering high-quality, maintainable, and scalable solutions.

In this article, we’ll embark on a journey through the essentials of unit testing in the ASP.NET Core Web API. From fundamental concepts to best practices and indispensable tools, we’ll explore how unit testing plays a pivotal role in fortifying the reliability and scalability of your applications. Curious to uncover the secrets to building more robust code?

Before diving into unit testing, let’s understand testing 🧪

What is Test-Driven Development (TDD) ?

TDD is a software development approach in which tests are written before the code that needs to be tested.

The TDD process typically follows a cycle of three main steps: Red, Green, and Refactor.

  1. Red: Define Test for Desired Functionality
    Write a test outlining the desired functionality before any code implementation. The test naturally fails since the code doesn’t exist.
  2. Green: Write the minimum code to Pass Test
    Implement the minimum code necessary to make the test pass. The focus is on achieving test success, not on the elegance or completeness of the solution.
  3. Refactor: Improve Code Structure and Readability
    After the test passes, refactor the code to enhance its structure, readability, and maintainability. The goal is to improve without changing behavior. Run tests again to ensure continued functionality.

This cycle is then repeated for each new piece of functionality that needs to be added or modified. The idea behind TDD is to have a comprehensive suite of tests that validate the correctness of the code. This practice helps catch defects early in the development process, encourages modular and loosely coupled code, and provides a safety net for future changes.

Key benefits of Test-Driven Development (TDD)

  • Early Detection of Defects: TDD helps catch bugs early in the development process, reducing the cost and effort required to fix them
  • Improved Code Quality
  • Confidence in Refactoring: Developers can make changes to the code with confidence, knowing that if they inadvertently introduce a defect, the tests will catch it

TDD is widely used in various programming languages and frameworks, and it has become a best practice in agile development methodologies. While it may require an initial shift in mindset for developers accustomed to writing code first, the long-term benefits in terms of code quality and maintainability make it a valuable practice in the software development process.

Types of Testing

  1. Unit Testing
  2. Integration Testing:
  3. Functional Testing:
  4. Load Testing:
  5. etc…

Understanding Unit Testing in the ASP.NET Core Web API

Unit testing involves the examination of individual units of code to verify that they function as expected. In the context of the ASP.NET Core Web API, these units are typically controllers, services, or other components responsible for specific functionalities. The primary goal is to isolate and test each unit independently, ensuring that it behaves correctly in different scenarios.

✅Ensure that individual components work as expected
✅Isolation from its infrastructure
✅Single action are tested
✅Not detect issues in the interaction between components
✅Simple to write and quick to run
✅Frameworks like xUnit.net, MSTest, Moq, or Nunit
✅Unit tests use fake or mock objects

Understanding Integration Testing in the ASP.NET Core Web API

Integration testing in the ASP.NET Core Web API is a crucial aspect of ensuring that different components of your application work together seamlessly. Unlike unit testing, which focuses on testing individual units in isolation, integration testing verifies the interactions and collaborations between these units. In the context of the ASP.NET Core Web API, integration testing typically involves testing how various parts of the API work together, including controllers, services, and database interactions.

✅Ensure that component interactions work as expected
✔ Databases,
✔ File System,
✔ Network Applications (send emails, etc..)
✔ Request-Response pipeline
✅No use of fakes or mock objects.
✅Don’t write integration tests for every possible permutation

Tools for Integration Testing
✔️xUnit or NUnit
✔️TestServer

Understanding Functional Testing in the ASP.NET Core Web API

Functional testing in the ASP.NET Core Web API is a critical phase in the software testing lifecycle that focuses on verifying whether the application’s features and functionalities align with the specified requirements. Unlike unit testing or integration testing, which concentrate on isolated components or component interactions, functional testing examines the system as a whole, ensuring that it behaves as expected from an end-user perspective. In the context of the ASP.NET Core Web API, functional testing encompasses testing the functionality of API endpoints, data processing, and overall system behavior.

✅These tests ensure that the application works as expected from the user’s perspective.

Tools for Functional Testing
✔️xUnit or NUnit
✔️TestServer

Understanding Load testing in the ASP.NET Core Web API

Load testing in the ASP.NET Core Web API is a crucial aspect of assessing how well your application performs under expected and peak loads. This type of testing helps identify performance bottlenecks, assess system scalability, and ensure that the API can handle a specified number of concurrent users or transactions.

✅A load test aims to determine whether or not a system can handle a specified load.

Load tests:
✔ The application runs under normal conditions.
✔ Test whether the application can handle a specified load of users for a certain scenario while still satisfying the response goal

Stress tests:
✔ The application will not run under normal conditions.
✔ Test the application's stability when running under extreme conditions
✔ Often for a long period of time
✔ Gradually increasing load on the application
✔ Limit the applications computing resources

Tools for Load Testing
✔️Apache JMeter
✔️ApacheBench (ab)
✔️Gatling, k6, Locust

Hooh❗Now, We have some basic ideas for testing

Let’s dive into A Practical Guide of Unit testing😍

Choosing a Unit Testing Framework

ASP.NET Core is flexible when it comes to unit testing frameworks. Popular choices include xUnit, NUnit, and MSTest. These frameworks provide a structured and efficient way to write and execute unit tests. For the purpose of this guide, we’ll use xUnit, a widely adopted framework known for its simplicity and extensibility.

xUnit: Three Attributes

Certainly! In xUnit, three main attributes are commonly used for creating unit tests: [Fact], [Theory], and [InlineData].

1. [Fact] Attribute:
The [Fact] attribute is used for simple, parameterless tests. It indicates that a method is a fact or a true statement that should always be true. Fact tests are used when there is no need for different inputs or scenarios.

public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}

public class CalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnsSum()
{
// Arrange
var calculator = new Calculator();

// Act
var result = calculator.Add(3, 4);

// Assert
Assert.Equal(7, result);
}
}

2. [Theory] Attribute:
The [Theory] attribute is used for parameterized tests. It allows you to write a single test method that can accept different sets of parameters, making it useful for testing the same logic with various inputs.

public class CalculatorTests
{
[Theory]
[InlineData(3, 4, 7)]
[InlineData(2, 5, 7)]
[InlineData(0, 0, 0)]
public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected)
{
// Arrange
var calculator = new Calculator();

// Act
var result = calculator.Add(a, b);

// Assert
Assert.Equal(expected, result);
}
}

3. [InlineData] Attribute:
The [InlineData] attribute is used in conjunction with the [Theory] attribute. It provides a way to specify sets of parameters for a parameterized test.

public class CalculatorTests
{
[Theory]
[InlineData(3, 4, 7)]
[InlineData(2, 5, 7)]
[InlineData(0, 0, 0)]
public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected)
{
// Arrange
var calculator = new Calculator();

// Act
var result = calculator.Add(a, b);

// Assert
Assert.Equal(expected, result);
}
}

Three Logical Parts of Unit Testing

Each test method normally consists of three logical parts
1. Arrange
Prepare the data that will be required for testing. For example, data is used for testing scenarios along with the expected result.

2. Act
Call the method that is being tested, making use of the data prepared in the arrangement. Here, it will return us the actual result.

3. Assert
Compare the expected results with the actual results to decide if the test passed or failed.

Best Practices for Naming Test Cases in Unit Testing

The naming of test cases in unit testing is an important aspect of clarity and maintainability. A well-named test case should convey the intent of the test and provide information about what scenario is being tested.

Here are some commonly used conventions for naming test cases in unit testing.

  1. CamelCase Naming:
    Use camelCase for test method names. For example, if you’re testing a method named CalculateTotal, the corresponding test method could be named CalculateTotal_ShouldReturnCorrectTotal.
  2. Descriptive Names:
    Make the test case name descriptive enough to help people understand the purpose of the test. Avoid generic names like Test1 or TestCase1. Instead, use names that explain what behavior is being tested.
  3. Use “Should” or “When”:
    Use “should” or “When” to indicate the expected behavior. For example, CalculateTotal_ShouldReturnCorrectTotal or CalculateTotal_WhenInputIsZero_ShouldReturnZero.
  4. Include test inputs:
    If applicable, include information about the inputs being tested. For example, Add_TwoPositiveNumbers_ShouldReturnPositiveSum.
  5. Avoid technical details:
    Avoid including technical details or implementation specifics in the test case name. The focus should be on the behavior being tested, not on the internal details of the code.
  6. Readable and clear:
    Make sure the test case name is readable and clear. Someone reading the test should be able to understand the purpose of the test without looking at the code.

Unit Testing IDEs

✔ Visual Studio
✔ Visual Studio Code

Setting Up Your Unit Testing Project

Create a separate project within your solution dedicated to unit testing.

This project should reference the main ASP.NET Core Web API project.

And include the necessary testing frameworks.
Required NugGet packages for Unit Testing

  1. Microsoft.NET.Test.Sdk
  2. xunit
  3. Moq
  4. AutoFixture
  5. FluentAssertions

Moq

Moq is a mocking framework for .NET that allows developers to create mock objects in their unit tests. Mocking is a technique used in unit testing to isolate the code being tested by creating simulated objects (mocks) that mimic the behavior of real objects. This is particularly useful when you want to control the behavior of dependencies or collaborators in your tests.

Moq is commonly used in conjunction with unit testing frameworks like MSTest, NUnit, or xUnit.

Reference: https://github.com/moq

AutoFixture

AutoFixture is a .NET library designed to help developers easily create test data for their unit tests. It simplifies the process of creating objects with realistic and randomized values, reducing the boilerplate code often associated with test data setup. AutoFixture is commonly used in conjunction with unit testing frameworks such as MSTest, NUnit, or xUnit.

References:
https://github.com/AutoFixture/AutoFixture
https://autofixture.github.io

FluentAssertions

FluentAssertions is a .NET library designed to enhance the readability and expressiveness of assertions in unit tests. It provides a fluent and natural language syntax for writing assertions, making it easier to understand the expected behavior of code under test. FluentAssertions can be used with popular testing frameworks like MSTest, NUnit, and xUnit.

References:
https://fluentassertions.com/
https://github.com/fluentassertions/fluentassertions

Configure your testing project to mimic the structure of your main project, making it easy to organize and execute tests.

Let’s dive into Testing🧑‍💻

Testing Controllers
Controllers are the entry points for HTTP requests in your Web API. Unit testing controllers involves verifying that the actions within them handle requests appropriately and return the expected results. Utilize xUnit’s testing attributes, such as [Fact] and [Theory], to define and execute tests for different scenarios.

public class UserControllerTests
{
[Fact]
public void GetUser_Returns_OkResult()
{
// Arrange
var controller = new UserController();

// Act
var result = controller.GetUser(1);

// Assert
Assert.IsType<OkObjectResult>(result);
}

// Add more test methods for different scenarios
}

Testing Services
Services encapsulate business logic within your Web API. Unit testing services involves isolating them from their dependencies and validating their behavior. Use mocking frameworks like Moq to create mock objects for dependencies, allowing you to focus solely on testing the service’s functionality.

public class UserServiceTests
{
[Fact]
public void GetUserById_Returns_User()
{
// Arrange
var userRepositoryMock = new Mock<IUserRepository>();
userRepositoryMock.Setup(repo => repo.GetUserById(It.IsAny<int>()))
.Returns(new User { Id = 1, Name = "John Doe" });

var userService = new UserService(userRepositoryMock.Object);

// Act
var user = userService.GetUserById(1);

// Assert
Assert.NotNull(user);
Assert.Equal("John Doe", user.Name);
}

// Add more test methods for different scenarios
}

Running the Test in Test Explorer

output

Note: You can find the source code for the Testing on my GitHub repository: https://github.com/JeslurRahman/

Conclusion

Unit testing in ASP.NET Core Web API is a fundamental practice for delivering high-quality, maintainable software. By investing time in creating comprehensive unit tests for controllers, services, and other components, developers can build confidence in their code and detect issues before they reach production. Embrace a test-driven mindset, choose the right testing framework, and diligently apply mocking and dependency injection techniques to elevate the quality of your ASP.NET Core Web API applications.

#aspdotnet core #testing #unittesting #moq #autofixture #fluentassertions

References

Thank you for reading! If you enjoyed this article and want to connect, feel free to check out my…
[LinkedIn ]
Jeslur Rahman
[Portfolio]Jeslur Rahman.Portfolio

Author: Jeslur Rahman😍

--

--