This article explores the complexities of unit testing the Masstransit Mediator Consumer, highlighting the importance of attaining thorough code coverage. Utilizing the Masstransit Mediator Unit Test enables developers to ensure their messaging components function correctly across different scenarios, thus enhancing the stability and performance of their applications.
A unit test is a piece of code that you write to assess your application’s code. Your web application won’t be aware of your test project, but your test project will rely on the application project it’s evaluating.
Thus, thoroughly testing your code aids in preventing bugs from reaching production. This article will guide you through unit testing an ASP.NET Core Web API and an MVC application.
But you can also hire .NET Developers for your project to Masstransit Mediator unit test with comprehensive code coverage.
Prerequisites
I anticipate that you possess familiarity with object-oriented programming principles in C#. Additionally, I presume you are acquainted with .NET Core API concepts, particularly the MVC pattern. Here some tools should be needed.
- Dotnet SDK 8
- Visual Studio
What is Masstransit?
Masstransit stands out as an open-source messaging library crafted for the .NET ecosystem. It specializes in facilitating communication and secure data exchange among applications, particularly in the domain of asynchronous and distributed applications.
This article will delve into harnessing the MassTransit library’s capabilities to construct resilient and scalable distributed applications.
Masstransit Mediator
CQRS, short for Command and Query Responsibility Segregation, is a pattern that divides read and update operations for a data store. Masstransit also offers support for this pattern. To implement CQRS, a separate consumer is necessary.
Therefore, in this article, we demonstrate how to test the consumer while achieving code coverage. Before writing any unit testing code, it’s essential to understand which project is being utilized for testing purposes.
Now, I’ll provide some code that we’ll be testing in this article. To begin, establish a .NET API project by following these steps.
Step 1: Create a project
Step 2: Give your project name as well.
Step 3: Provide additional information.
Step 4: Now, let’s install some NuGet packages into your project.
Step 5: Now, this code provides a service (TestService) that generates a sequence of integers within a specified range. The interface ITestService defines the contract for this service, allowing for abstraction and easier dependency management.
public interface ITestService
{
IEnumerable GetRangedNumbers(int number);
}
public class TestService : ITestService
{
public TestService() {
}
public IEnumerable GetRangedNumbers(int number)
{
return Enumerable.Range(0, number);
}
}
Step 6: Now create a DTO for the request filter
public class TestDTO
{
public int Limit { get; init; }
}
Step 7: This code configures Masstransit for in-memory messaging and Mediator for handling message consumers. It also registers TestService as a scoped service.
builder.Services.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.UsingInMemory((context, config) =>
{
config.ConfigureEndpoints(context);
});
});
builder.Services.AddMediator(cfg =>
{
cfg.AddConsumers(AppDomain.CurrentDomain.Load("MasstransitConsumer"));
});
builder.Services.AddScoped();
Step 8: This class TestConsumer implements the Masstransit IConsumer<TestDTO> interface, indicating that it consumes messages of type TestDTO. Inside the Consume method, it retrieves a range of random numbers using the injected ITestService, based on the Limit property of the consumed message.
Finally, it responds asynchronously with a TestResponse containing the generated random numbers.
public class TestResponse
{
public IEnumerable Data { get; set; }
}
public class TestConsumer : IConsumer
{
private readonly ITestService _service;
public TestConsumer(ITestService service)
{
_service = service;
}
public async Task Consume(ConsumeContext context)
{
var randomNumbers = _service.GetRangedNumbers(context.Message.Limit);
await context.RespondAsync(new TestResponse
{
Data = randomNumbers,
});
}
}
Step 9: This TestController is an API controller responsible for handling HTTP GET requests. It uses the Mediator pattern via the injected IMediator instance to handle the request. The GetRandomNumbers action method receives a query parameter TestDTO from the request, which contains a Limit property.
It then creates a request client using the Mediator to send a request to the appropriate consumer. Finally, it returns the response received from the consumer as an HTTP OK result.
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
private readonly IMediator _mediator;
public TestController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task GetRandomNumbers([FromQuery] TestDTO query)
{
var response = await _mediator.CreateRequestClient()
.GetResponse(new TestDTO
{
Limit = query.Limit,
});
return Ok(response.Message);
}
}
Step 10: Now, open the browser and test the endpoint to see if it’s being hit or not.
Our API is functioning properly. Now, let’s proceed to test the consumer. Initially, we need to create a separate project for unit testing. We’ll also establish a unit test project using NUnit. Follow some steps for better understanding.
Step 1: Create a Unit Test Project in the same solution as MasstransitConsumer.
Step 2: Find NUnit.
Step 3: Give a name.
Now, take a look at the created project. We need to install some necessary NuGet packages.
Step 4: Follow packages you have to install.
Step 5: Include the API project in the unit test project as a shared project reference.
Step 6: Now select the project.
Step 7: Now, adjust the UnitTest class that has already been created. If it hasn’t been generated automatically, you’ll need to create a class named UnitTest as an example. Also add annotation [ExcludeFromCodeCoverage]
[ExcludeFromCodeCoverage]
public class UnitTest
{
private readonly ITestService testServiceMoq;
public UnitTest()
{
testServiceMoq = Substitute.For();
}
}
Step 8: We’ll adhere to a straightforward rule for Unit Testing.
Arrange : This entails arranging or setting up the required parameters for the test.
Act: This involves simply executing the action, such as calling a method or invoking a controller.
Assert: Means just evaluate the result.
Now, There is a unit test method written using NUnit for testing the functionality of the GetRandomNumbers action method in the TestController. Here’s a brief explanation of each section:
1. Arrange:
- Set up the test scenario by defining the limit for generating random numbers
- Create a range of numbers from 0 to the specified limit
- Configure the behavior of the mocked ITestService instance (testServiceMoq) to return the generated numbers when called with the specified limit
- Set up the Masstransit test harness and add the TestConsumer as a consumer
2. Act:
- Perform the action being tested, which is sending a request to the controller’s GetRandomNumbers action method using the Masstransit test harness
3. Assert:
- Verify that the response received from the controller is not null
- Ensure that the data in the response message has the expected count, which should match the specified limit
4. Cleanup:
- Stop the Masstransit test harness
- Dispose of the service provider after the test execution
[Test]
public async Task Get_Random_Numbers_Should_Return_List()
{
//////////Arrange
int limit = 100;
var numbers = Enumerable.Range(0, limit);
testServiceMoq.GetRangedNumbers(limit).Returns(numbers);
//Configure masstransit consumer with masstransit harness
await using var provider = new ServiceCollection()
.AddScoped(provider => testServiceMoq)
.AddMassTransitTestHarness(opt =>
{
opt.AddConsumer();
})
.BuildServiceProvider(true);
//Register the Test service
var harness = provider.GetRequiredService();
await harness.Start();
//Request with Test DTO
var client = harness.GetRequestClient();
////////////Act
//Response
var response = await client.GetResponse(new TestDTO
{
Limit = limit
});
///////////Assert
response.Message.Should().NotBeNull();
response.Message.Data.Should().HaveCount(limit);
await harness.Stop();
await provider.DisposeAsync();
}
Step 9: Our test code is in good shape. Now, we need to set up code coverage tools. For educational purposes, we’ll utilize a free tool called Fine Code Coverage, which is available as a Visual Studio extension manager. Look for Fine Code Coverage in the extensions.
In my case, the extension is already installed. If you haven’t installed it yet, you’ll need to do so first. After the installation is finished, restart Visual Studio. Then, the extension should work properly.
Step 10: To run the test method, navigate to Test -> Test Explorer.
Step 11: Have a look at Test Explorer. Follow the red arrow or you can right click the method name and click test option.
Step 12: Now, take a look to see if the test code has passed. If it hasn’t, review your code and address any errors indicated.
Step 13: Let’s open the code coverage to examine how your code was tested. Navigate to View -> Other Windows -> See Fine Code Coverage. This is the extension you installed earlier.
Step 14: Review the code coverage results after testing the method.
Conclusion
This article hasn’t encompassed every aspect of professional unit testing with Masstransit mediator. While working with microservices, there may arise a need for these techniques, but here you’ve gained familiarity with some fundamental concepts.
There exist several guidelines and best practices to aim for when crafting unit tests and achieving code coverage. Adhering to these practices will undoubtedly simplify your work (and that of your fellow developers).