Dependency Injection is one of the most elegant tool to inject dependencies at runtime, which makes the application more loosely-coupled. It also helps in building modularized code that improves testability by enabling unit-testing on modules.
In this post, I will be creating a complicated dependency setup to explain the idea of how dependencies can be injected using an Azure Function. Then, I will mock these dependencies to test the setup using Moq and xUnit. This post will help you to understand how dependency injection setup can be seamlessly simulated using these amazing frameworks.
You can use the below GitHub Repo for referring the complete code mentioned here.
azure-dev-projects/MoqXunitWithDI
First, let’s understand the modularized application which we will be building.
Table of contents
Building Custom Application with Dependencies
We need to build a complicated setup of dependencies to explain the usage of dependency injection. In order to do this, we will be implementing the below shown setup.
We will be creating a FooModule
class library to get all the required classes in one place as shown below.
We will first start with our interfaces.
-
IFooCollection
→ This interface will help in providing all theFooRegistration
objectspublic interface IFooCollection { public IEnumerable<FooRegistration> GetAllRegistrations(); }
-
IFooWork
→ This interface will help in providingFooResult
object after doing some simulated checkingpublic interface IFooWork { Task<FooResult> CheckFooAsync(string param1, string param2, FooContext context, FooToken fooToken); }
-
IFooLogicImplementer
→ This interface will help in designing ourFooLogicImplementer
that will giveFinalResult
after executing some logicpublic interface IFooLogicImplementer { Task<FinalResult> ExecuteLogic(); }
These interfaces are the dependencies using which we will implement our FooLogicImplementer
as shown below.
//FooLogicImplementer.cs
public class FooLogicImplementer: IFooLogicImplementer
{
private readonly List<FooRegistration> _foos = new List<FooRegistration>();
public FooLogicImplementer(IEnumerable<IFooCollection> data)
{
foreach(var foo in data)
{
_foos.AddRange(foo.GetAllRegistrations());
}
}
// simulate logic
// We need to test this functionality using unit test
public async Task<FinalResult> ExecuteLogic()
{
List<FooResult> data = new List<FooResult>();
foreach(var _foo in _foos)
{
data.Add(await _foo.Exec());
}
var isAllGood = true;
foreach(var result in data)
{
if(result.Val == FooResultVals.BadResult)
{
isAllGood = false;
}
}
if (isAllGood)
{
return new FinalResult
{
FinalRes = "Good",
Data = data
};
}
else
{
return new FinalResult
{
FinalRes = "Bad",
Data = data
};
}
}
}
The above implementation is following below process:
- Collection of
IFooCollection
will be injected using dependency injection. - Fire
GetAllRegistrations
of eachIFooCollection
to getFooRegistration
objects. - For each of the
FooRegistration
objects, callexec
function and collectFooResult
objects. - If all the results are
Good
, then set final result to beGood
- Otherwise, if any of the result is
Bad
, the set final result to beBad
As you can see, this implementation is dependent on several interfaces to derive the final result to be good or bad. Hence, in order to test this implementation, we would need to mock these dependencies.
You can view/download the entire module using below GitHub URL:
azure-dev-projects/MoqXunitWithDI/FooModule
Now, we are ready to test this module. We will first test it using Azure Functions to see if our dependencies can be injected efficiently and then use unit testing.
Testing Custom Application with Dependencies
Our objective is to test the
FooLogicImplementer
class and itsExecuteLogic
functionality by verifyingFinalResult
.
Test using Azure Function
We can test whether our dependencies are working by creating an Azure function application. The project can be viewed using the below GitHub URL:
azure-dev-projects/MoqXunitWithDI/FooLogicFnApp
We will inject the above dependencies during startup using our custom implementations as shown below.
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
// inject dependencies
builder.Services.AddScoped<IFooCollection, FooLogic1>(sp =>
{
return new FooLogic1(new FooWork1());
});
builder.Services.AddScoped<IFooCollection, FooLogic2>(sp =>
{
return new FooLogic2(new FooWork2());
});
// inject implementer
builder.Services.AddScoped<IFooLogicImplementer, FooLogicImplementer>();
}
}
Here, FooLogic1
derives from IFooCollection
and implements GetAllRegistrations
function to generate List<FooRegistration>
as shown below.
public class FooLogic1 : IFooCollection
{
private readonly IFooWork _fooWork;
public FooLogic1(IFooWork fooWork)
{
_fooWork = fooWork;
}
public IEnumerable<FooRegistration> GetAllRegistrations()
{
return new List<FooRegistration>
{
new FooRegistration("logic_reg_1_1", _fooWork, new FooToken(), new List<string> {"tag1", "tag2"}),
new FooRegistration("logic_reg_1_2", _fooWork, new FooToken(), new List<string> {"tag1", "tag2"})
};
}
}
Similarly, FooLogic2
can be implemented to generate List<FooRegistration>
.
Now, each FooRegistration
takes IFooWork
instance which needs to be provided. This instance sets whether it is either a good result or bad result as shown below for FooWork1
. Here, it is setting a GoodResult
.
public class FooWork1 : IFooWork
{
public Task<FooResult> CheckFooAsync(string param1, string param2, FooContext context, FooToken fooToken)
{
return Task.FromResult(new FooResult(param1, param2, FooResultVals.GoodResult));
}
}
Similarly, we can create FooWork2
that also derives from IFooWork
and return a FooResult
with a BadResult
as shown below.
public class FooWork2 : IFooWork
{
public Task<FooResult> CheckFooAsync(string param1, string param2, FooContext context, FooToken fooToken)
{
return Task.FromResult(new FooResult(param1, param2, FooResultVals.BadResult));
}
}
We can now start and test our function app by hitting the endpoint using postman or any other REST client. Since one of our result returned by our IFooWork
is good and one is bad, we should get the final result as bad result as shown below.
This shows that our dependencies are being injected correctly and the logic of FooLogicImplementer
is working as required. But, we need to test it for other scenarios as well, such as,
- If all
IFooWork
are good result, then final result should be good. - If some
IFooWork
are good result and some are bad, then final result should be bad. - If all
IFooWork
are bad result, then final result should be bad. - If 1
IFooCollection
is registered, then 1 is checked properly. - If 2
IFooCollection
are registered, then 2 are checked properly.
Looking at these scenarios, I hope you can clearly see the problem with Azure function based testing. We need to change our code every time - create the required implementation for the scenario, run the application, hit it via a REST client and then view the results. Moreover, this approach is unlikely to scale well when we’ll have 10-20 scenarios.
So, a better way is to do unit-testing. We can create unit tests for each of the above scenarios, and then run them in isolation to verify the functionality. But, how do you inject these dependencies to test our FooLogicImplementer
class? That’s where Moq
library will enter!
Test using xUnit and Moq
First, we will create a unit test C# project (.NET core 3.1 LTS) and install following nuget packages:
- Moq (4.16.1)
- xunit (2.4.1)
- xunit.runner.visualstudio (2.4.3)
You can view the entire test project using the below GitHub URL:
azure-dev-projects/MoqXunitWithDI/UnitTestLogicImplementer
Now, if you are wondering why xUnit
has gained more popularity than MSTest
- it is mostly because of the support of Theory
attribute and its pluggable functionality in any project.
For more details, checkout this blog: The xUnit tool has gained popularity over MSTest
So, let’s try to create our 1 first unit test that will cover the scenario:
If all
IFooWork
are good result, then final result should be good.
In order to test the above scenario, we need to create an object of FooLogicImplementer
class. But, if you look at the constructor, it needs IEnumerable<IFooCollection> data
to instantiate. Hence, we will mock these dependencies so that we can create the required object.
-
Mock
IFooWork
to get aGoodResult
.// setup work 1 to be good result _work1 = new Mock<IFooWork>(); _work1.Setup(s => s.CheckFooAsync("logic_reg_1_1", It.IsAny<string>(), It.IsAny<FooContext>(), It.IsAny<FooToken>())) .Returns(Task.FromResult(new FooResult("logic_reg_1_1", It.IsAny<string>(), FooResultVals.GoodResult)));
This setup will ensure that whenever mocked object of
IFooWork
is used to fireCheckFooAsync
function with first parameter as"logic_reg_1_1"
and other parameters could be any valid value, then it should result aFooResult
that will haveGoodResult
. So, rather than implementing the logic, we have mocked the inputs and outputs as per our requirement. - Similarly, Mock another
IFooWork
to get a good result. -
Create a
FooRegistration
collection which will take these mockedIFooWork
objects.var data = new List<FooRegistration>() { new FooRegistration("logic_reg_1_1", _work1.Object, new FooToken(), new List<string> {"tag1", "tag2"}), new FooRegistration("logic_reg_1_2", _work2.Object, new FooToken(), new List<string> {"tag1", "tag2"}) };
-
Mock the
IFooCollection
which will return the above collection whenGetAllRegistrations
is fired._fooCollection.Setup(s => s.GetAllRegistrations()).Returns(data);
-
Using the above mocked dependencies, create an object of
FooLogicImplementer
and execute its logic function.// act var testImplementer = new FooLogicImplementer(new List<IFooCollection> { _fooCollection.Object }); var res = await testImplementer.ExecuteLogic();
-
Assert the final result.
Xunit.Assert.Equal("Good", res.FinalRes);
So, the final implementation of the scenario can be stated as follows:
public class FooLogicImplementerTests
{
private readonly Mock<IFooCollection> _fooCollection;
private Mock<IFooWork> _work1;
private Mock<IFooWork> _work2;
public FooLogicImplementerTests()
{
_fooCollection = new Mock<IFooCollection>();
}
[Fact]
public async void If_All_Good_Return_Good()
{
// mock dependencies
// setup work 1 to be good result
_work1 = new Mock<IFooWork>();
_work1.Setup(s => s.CheckFooAsync("logic_reg_1_1", It.IsAny<string>(), It.IsAny<FooContext>(), It.IsAny<FooToken>()))
.Returns(Task.FromResult(new FooResult("logic_reg_1_1", It.IsAny<string>(), FooResultVals.GoodResult)));
// setup work 2 to be good result
_work2 = new Mock<IFooWork>();
_work2.Setup(s => s.CheckFooAsync("logic_reg_1_2", It.IsAny<string>(), It.IsAny<FooContext>(), It.IsAny<FooToken>()))
.Returns(Task.FromResult(new FooResult("logic_reg_1_2", It.IsAny<string>(), FooResultVals.GoodResult)));
// Add some FooRegistrations
var data = new List<FooRegistration>()
{
new FooRegistration("logic_reg_1_1", _work1.Object, new FooToken(), new List<string> {"tag1", "tag2"}),
new FooRegistration("logic_reg_1_2", _work2.Object, new FooToken(), new List<string> {"tag1", "tag2"})
};
_fooCollection.Setup(s => s.GetAllRegistrations()).Returns(data);
// act
var testImplementer = new FooLogicImplementer(new List<IFooCollection> { _fooCollection.Object });
var res = await testImplementer.ExecuteLogic();
// assert
Xunit.Assert.Equal("Good", res.FinalRes);
}
}
Similarly, we can do mocking of these dependencies again and test for all of the above stated scenarios as shown below.
The implementation of other test cases are very similar to the one showed here. You can review them using the below URL:
MoqXunitWithDI/UnitTestLogicImplementer/FooLogicImplementerTests.cs
This completes the idea of using Moq and xUnit with complicated dependency injection setup. As you can see, it can be really helpful to create unit tests for testing multiple scenarios, rather than updating the code again and again to cover all cases.
Cheers!
Leave a comment