xUnit Unit Testing • Posted one year ago
So far in our journey of writing unit tests for void methods or command methods, which are meant to invoke some command onto the system, we have been looking at the different types of methods and how writing unit tests would differ for each method types - the Interation tests and the Computation tests. For a Computation test we can simply assert the functionality by its return value and judge if the functionality has any issues.
But for Interaction tests where there are no return types, we assert the functionality of such methods by verifying whether a particular method has been called or not.
Let's assume the ReaderController has method Read() which internally invokes the ReaderFactory for a Reader instance based on the tierId parameter value.
public class ReaderController : Controller
{
[Route("{tierId}")]
public IEnumerable<Reader> Read(int tierId)
{
var reader = factory.Create(tierId);
return reader.ReadContent();
}
}
We have previously written unit tests on the ReaderFactory class to assert the return object type for a passed in tierId. In this let's write unit tests on the method in ReaderController which has a dependency on the ReaderFactory via IReaderFactory interface injection.
Let's create a new file ReaderController_UnitTests which holds the same.
We can test two functional flows:
The former test would be a query test and the later is a verification test and hence an interaction test.
Let's add the package Moq to use in this project:
> dotnet add package Moq
Let's add test logic to the two test scenarios:
public class ReaderController_UnitTests
{
[Fact]
public void Read_TierOneReader_WhenCalled_ReturnsNonEmptyList()
{
//Arrange
var factoryMoq = new Mock<IReaderFactory>();
factoryMoq.Setup(x => x.Create(It.IsAny<int>()))
.Returns(new TierOneReader());
var controller = new ReaderController(factoryMoq.Object);
//Act
var data = controller.Read(1).ToList();
//Assert
Assert.True(data.Count() > 0);
}
[Fact]
public void Read_NullReader_WhenCalled_InvokesCreate()
{
//Arrange
var factoryMoq = new Mock<IReaderFactory>();
factoryMoq.Setup(x => x.Create(It.IsAny<int>()))
.Returns(new NullReader());
var controller = new ReaderController(factoryMoq.Object);
//Act
var data = controller.Read(1).ToList();
//Assert
factoryMoq.Verify(x => x.Create(It.IsAny<int>()));
factoryMoq.Verify(x => x.Print(It.IsAny<string>()), Times.AtLeastOnce);
}
}
We have used a Mock object of ReaderFactory to write these tests; in the actual flow we would inject an instance of ReaderFactory to the ReaderController class through constructor injection and we would like to use a mock implementation of the IReaderFactory which is substituted by a real ReaderFactory instance during runtime.
The second method Read_NullReader_WhenCalled_InvokesCreate() is the actual interaction method, wherein we verify whether the call to Create() method is invoked or not. There's another call to Print() method on the next line, which is actually called when there's no valid Reader available for the tierId.
public class ReaderFactory : IReaderFactory
{
public IReader Create(int tierId)
{
if (tierId == 1)
return new TierOneReader();
else if (tierId == 2)
...
else
Print("No Matching Readers Found");
return new NullReader();
}
public void Print(string message)
{
Console.WriteLine(message);
}
}
In this scenario we verify whether a call to Print() has been made, if the tierId has no valid if-else branch.
The Moq method factoryMoq.Verify(x => x.Print(It.IsAny
Running the Tests:
Before test execution, we would need to add reference for the project ./Api/ReaderFactoryApp.Api.csproj within the project ./Api/ReaderFactoryApp.Tests.csproj
we can do it by the command,
> dotnet add ./ReaderFactoryApp.Tests/ReaderFactoryApp.Tests.csproj reference ./Api/ReaderFactoryApp.Api.csproj
This adds a reference to the API project in test project and should remove all the reference errors. We might also get an error when accessing the controller class within the test project, we can resolve it by adding a reference to the package Microsoft.AspNetCore.Mvc.Core
> dotnet add package Microsoft.AspNetCore.Mvc.Core
This should solve all the build errors, and we're good to go. Let's build the projects by running the command against the solution:
> dotnet clean && dotnet build
We can observe that the Editor shows options for Run Test on top of each method and each class. We can run the tests by either clicking on the link above the methods
or by running a command:
> dotnet test
And the result would be as below:
In this way, we can write unit tests for command methods as well as query methods by using xUnit, Moq and dotnet CLI.
You might also be interested in these posts on Unit Testing and xUnit:
Writing Mocking unit tests in ASP.NET Core using xUnit and Moq
How to write unit tests in ASP.NET Core using xUnit - Getting Started
Enjoying my posts?
You can now show me your support! 😊
moq void method unit test void method c# unit test void method c# moq create unit test for void method c# xunit test void method how to test void methods c# assert void method c# unit test case for void method in c# unit test for void method c# xunit assert void method c# unit test void method moq mock void method unit test void method moq return void how to write unit test for void method c# test void method c# unit testing void methods assert for void method c# unit testing void methods c# xunit test void method c# how to unit test methods that return void c# unit test verify method called unit testing c# asp.net core