how to unit test asp.net core application with constructor dependency injection
C#Unit Testingasp.net CoreTestingDependency InjectionC# Problem Overview
I have a asp.net core application that uses dependency injection defined in the startup.cs class of the application:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:FotballConnection:DefaultConnection"]));
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserRoleRepository, UserRoleRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<ILoggingRepository, LoggingRepository>();
// Services
services.AddScoped<IMembershipService, MembershipService>();
services.AddScoped<IEncryptionService, EncryptionService>();
// new repos
services.AddScoped<IMatchService, MatchService>();
services.AddScoped<IMatchRepository, MatchRepository>();
services.AddScoped<IMatchBetRepository, MatchBetRepository>();
services.AddScoped<ITeamRepository, TeamRepository>();
services.AddScoped<IFootballAPI, FootballAPIService>();
This allows something like this:
[Route("api/[controller]")]
public class MatchController : AuthorizedController
{
private readonly IMatchService _matchService;
private readonly IMatchRepository _matchRepository;
private readonly IMatchBetRepository _matchBetRepository;
private readonly IUserRepository _userRepository;
private readonly ILoggingRepository _loggingRepository;
public MatchController(IMatchService matchService, IMatchRepository matchRepository, IMatchBetRepository matchBetRepository, ILoggingRepository loggingRepository, IUserRepository userRepository)
{
_matchService = matchService;
_matchRepository = matchRepository;
_matchBetRepository = matchBetRepository;
_userRepository = userRepository;
_loggingRepository = loggingRepository;
}
This is very neat. But becomes a problem when I want to unit test. Because my test library does not have a startup.cs where I setup dependency injection. So a class with these interfaces as params will just be null.
namespace TestLibrary
{
public class FootballAPIService
{
private readonly IMatchRepository _matchRepository;
private readonly ITeamRepository _teamRepository;
public FootballAPIService(IMatchRepository matchRepository, ITeamRepository teamRepository)
{
_matchRepository = matchRepository;
_teamRepository = teamRepository;
In the code above, in the test library, _matchRepository and _teamRepository, will just be null. :(
Can I do something like ConfigureServices, where I define dependency injection in my test library project?
C# Solutions
Solution 1 - C#
Although @Kritner's answer is correct, I prefer the following for code integrity and better DI experience:
[TestClass]
public class MatchRepositoryTests
{
private readonly IMatchRepository matchRepository;
public MatchRepositoryTests()
{
var services = new ServiceCollection();
services.AddTransient<IMatchRepository, MatchRepositoryStub>();
var serviceProvider = services.BuildServiceProvider();
matchRepository = serviceProvider.GetService<IMatchRepository>();
}
}
Solution 2 - C#
A simple way, I wrote a generic dependency resolver helper class and then built the IWebHost in my unit test class.
Generic Dependency Resolver
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class DependencyResolverHelper
{
private readonly IWebHost _webHost;
/// <inheritdoc />
public DependencyResolverHelper(IWebHost webHost) => _webHost = webHost;
public T GetService<T>()
{
var serviceScope = _webHost.Services.CreateScope();
var services = serviceScope.ServiceProvider;
try
{
var scopedService = services.GetRequiredService<T>();
return scopedService;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
Unit Test Project:
[TestFixture]
public class DependencyResolverTests
{
private DependencyResolverHelper _serviceProvider;
public DependencyResolverTests()
{
var webHost = WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.Build();
_serviceProvider = new DependencyResolverHelper(webHost);
}
[Test]
public void Service_Should_Get_Resolved()
{
//Act
var YourService = _serviceProvider.GetService<IYourService>();
//Assert
Assert.IsNotNull(YourService);
}
}
Solution 3 - C#
Your controllers in .net core have dependency injection in mind from the start, but this does not mean you are required to use a dependency injection container.
Given a simpler class like:
public class MyController : Controller
{
private readonly IMyInterface _myInterface;
public MyController(IMyInterface myInterface)
{
_myInterface = myInterface;
}
public JsonResult Get()
{
return Json(_myInterface.Get());
}
}
public interface IMyInterface
{
IEnumerable<MyObject> Get();
}
public class MyClass : IMyInterface
{
public IEnumerable<MyObject> Get()
{
// implementation
}
}
So in your app, you're using the dependency injection container in your startup.cs
, which does nothing more than provide a concretion of MyClass
to use when IMyInterface
is encountered. This does not mean it is the only way of getting instances of MyController
however.
In a unit testing scenario, you can (and should) provide your own implementation (or mock/stub/fake) of IMyInterface
as so:
public class MyTestClass : IMyInterface
{
public IEnumerable<MyObject> Get()
{
List<MyObject> list = new List<MyObject>();
// populate list
return list;
}
}
and in your test:
[TestClass]
public class MyControllerTests
{
MyController _systemUnderTest;
IMyInterface _myInterface;
[TestInitialize]
public void Setup()
{
_myInterface = new MyTestClass();
_systemUnderTest = new MyController(_myInterface);
}
}
So for the scope of unit testing MyController
, the actual implementation of IMyInterface
does not matter (and should not matter), only the interface itself matters. We have provided a "fake" implementation of IMyInterface
through MyTestClass
, but you could also do this with a mock like through Moq
or RhinoMocks
.
Bottom line, you do not actually need the dependency injection container to accomplish your tests, only a separate, controllable, implementation/mock/stub/fake of your tested classes dependencies.
Solution 4 - C#
If you are using the Program.cs
+ Startup.cs
convention and want to get this working quickly you can reuse your existing host builder with a one-liner:
using MyWebProjectNamespace;
public class MyTests
{
readonly IServiceProvider _services =
Program.CreateHostBuilder(new string[] { }).Build().Services; // one liner
[Test]
public void GetMyTest()
{
var myService = _services.GetRequiredService<IMyService>();
Assert.IsNotNull(myService);
}
}
Sample Program.cs
file from web project:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace MyWebProjectNamespace
{
public class Program
{
public static void Main(string[] args) =>
CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Solution 5 - C#
You can use asp.net core DI and inject mocked instance objects in your tests. Here is a full working example :
For the sake of the example :
- I only kept the
IMatchService
dependency from the code snippet of the initial question - I added a
DoSomething
action in theMatchController
so that there is someting to test. - I added an
Add
method to theIMatchService
and theMatchService
classes so that there is soemthing to mock.
Please note that the methods that will have a Setup
with Moq
should be virtual.
[Route("api/[controller]")]
public class MatchController : AuthorizedController
{
private readonly IMatchService _matchService;
public MatchController(IMatchService matchService)
{
_matchService = matchService;
}
public virtual int DoSomething()
{
return _matchService.Add(1, 2);
}
}
public interface IMatchService
{
int Add(int a, int b);
}
public class MatchService : IMatchService
{
public virtual int Add(int a, int b)
{
return a + b;
}
}
It's always possible to get the Mock by calling the Mock.Get
method.
For conveniance for each dependency, I create two properties like MatchService
and MockedMatchService
.
public class MyTests
{
protected IMatchService MatchService { get; set; }
protected Mock<IMatchService> MockedMatchService => Mock.Get(MatchService);
private IServiceProvider ServicesProvider { get; set; }
[SetUp]
public void SetupBeforeEachTest()
{
// Configure DI container
ServiceCollection services = new ServiceCollection();
ConfigureServices(services);
ServicesProvider = services.BuildServiceProvider();
// Use DI to get instances of IMatchService
MatchService = ServicesProvider.GetService<IMatchService>();
}
// In this test I mock the Add method of the dependency (IMatchService) so that it returns a value I choose
[Test]
public void TestMethod()
{
// Prepare
var matchController = ServicesProvider.GetService<MatchController>();
int expectedResult = 5;
MockedMatchService.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>())).Returns(expectedResult);
// Act - This will call the real DoSomething method because the MatchController has comes from a Mock with CallBase = true
int result = matchController.DoSomething();
// Check
Assert.AreEqual(expectedResult, result);
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMatchService>();
services.AddScoped<MatchController>();
}
}
Solution 6 - C#
Why would you want to inject those in a test class? You would usually test the MatchController, for example, by using a tool like RhinoMocks to create stubs or mocks. Here's an example using that and MSTest, from which you can extrapolate:
[TestClass]
public class MatchControllerTests
{
private readonly MatchController _sut;
private readonly IMatchService _matchService;
public MatchControllerTests()
{
_matchService = MockRepository.GenerateMock<IMatchService>();
_sut = new ProductController(_matchService);
}
[TestMethod]
public void DoSomething_WithCertainParameters_ShouldDoSomething()
{
_matchService
.Expect(x => x.GetMatches(Arg<string>.Is.Anything))
.Return(new []{new Match()});
_sut.DoSomething();
_matchService.AssertWasCalled(x => x.GetMatches(Arg<string>.Is.Anything);
}
Solution 7 - C#
I worked over @madjack and @Kritner's answers and made my
> Basic Inheritable Base Test Class for Dependency Injection
Just register your services inside of it and inherite.
public class BaseTester
{
protected IProductService _productService;
protected IEmployeeService _employeeService;
public BaseTester()
{
var services = new ServiceCollection();
services.AddTransient<IProductService, ProductService>();
services.AddTransient<IEmployeeService, EmployeeService>();
var serviceProvider = services.BuildServiceProvider();
_productService = serviceProvider.GetService<IProductService>();
_employeeService = serviceProvider.GetService<IEmployeeService>();
}
}
Solution 8 - C#
Improved solution
I improved madjack's solution by wrapping it in single abstract class
and adding four methods (including two async
equivalents) with callbacks as parameters. GetRequiredScopedService<TSvc>()
is using private static
property services
for caching now, so derived classes don't create new instances over and over. Another optimization is making host
static
, so we don't build it every time in derived classes. I also removed pointless try/catch:
public abstract class TestWithDependencyInjection
{
private static readonly IHost host =
Program.CreateHostBuilder(Constants.CommandArgs).Build();
private static readonly IList<object> services =
new List<object>();
private IServiceScope svcScope;
protected async Task<TResult> UseSvcAsync<TSvc, TResult>(
Func<TSvc, Task<TResult>> callback,
bool shouldBeDisposed = true)
{
var scopedSvc = GetRequiredScopedService<TSvc>();
TResult result = await callback(scopedSvc);
if(shouldBeDisposed)
svcScope.Dispose();
return result;
}
protected async Task UseSvcAsync<TSvc>(
Func<TSvc, Task> callback)
{
var scopedSvc = GetRequiredScopedService<TSvc>();
await callback(scopedSvc);
svcScope.Dispose();
}
protected TResult UseSvc<TSvc, TResult>(
Func<TSvc, TResult> callback, bool shouldBeDisposed = true)
{
var scopedSvc = GetRequiredScopedService<TSvc>();
TResult result = callback(scopedSvc);
if(shouldBeDisposed)
svcScope.Dispose();
return result;
}
protected void UseSvc<TSvc>(Action<TSvc> callback)
{
var scopedSvc = GetRequiredScopedService<TSvc>();
callback(scopedSvc);
svcScope.Dispose();
}
private TSvc GetRequiredScopedService<TSvc>()
{
var requiredScopedSvc = (TSvc)services.SingleOrDefault(
svc => svc is TSvc);
if (requiredScopedSvc != null)
return requiredScopedSvc;
svcScope = host.Services.CreateScope();
requiredScopedSvc = svcScope.ServiceProvider
.GetRequiredService<TSvc>();
services.Add(requiredScopedSvc);
return requiredScopedSvc;
}
}
async
result
from used injected service:
Example of returning int foobarsCount = await UseSvcAsync<IFoobarSvc, int>(
foobarSvc => foobarSvc.GetCountAsync());
Additional information
I added optional shouldBeDisposed
argument set on true
to methods returning TResult
and Task<TResult>
in case, when you want to use same instance of service outside of callback's body:
IFoobarSvc foobarSvc = UseSvc<IFoobarSvc, IFoobarSvc>(
foobarSvc => foobarSvc, false);