Net Core: Execute All Dependency Injection in Xunit Test for AppService, Repository, etc

18,477

Solution 1

You are mixing unit test with integration test. TestServer is for integration test and if you want to reuse Startup class to avoid register dependencies again, you should use HttpClient and make HTTP call to controller and action that use IDepartmentAppService.

If you want do unit test, you need to setup DI and register all needed dependencies to test IDepartmentAppService.

Using DI through Test Fixture:

public class DependencySetupFixture
{
    public DependencySetupFixture()
    {
         var serviceCollection = new ServiceCollection();
         serviceCollection.AddDbContext<SharedServicesContext>(options => options.UseInMemoryDatabase(databaseName: "TestDatabase"));
         serviceCollection.AddTransient<IDepartmentRepository, DepartmentRepository>();
         serviceCollection.AddTransient<IDepartmentAppService, DepartmentAppService>();

         ServiceProvider = serviceCollection.BuildServiceProvider();
    }

    public ServiceProvider ServiceProvider { get; private set; }
}

public class DepartmentAppServiceTest : IClassFixture<DependencySetupFixture>
{
    private ServiceProvider _serviceProvide;

    public DepartmentAppServiceTest(DependencySetupFixture fixture)
    {
        _serviceProvide = fixture.ServiceProvider;
    }

    [Fact]
    public async Task Get_DepartmentById_Are_Equal()
    {
        using(var scope = _serviceProvider.CreateScope())
        {   
            // Arrange
            var context = scope.ServiceProvider.GetServices<SharedServicesContext>();
            context.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
            context.SaveChanges();

            var departmentAppService = scope.ServiceProvider.GetServices<IDepartmentAppService>();

            // Act
            var departmentDto = await departmentAppService.GetDepartmentById(2);

            // Arrange
            Assert.Equal("123", departmentDto.DepartmentCode);           
        }
    }
}

Using dependency injection with unit test is not good idea and you should avoid that. by the way if you want don't repeat your self for registering dependencies, you can wrap your DI configuration in another class and use that class anywhere you want.

Using DI through Startup.cs:

public class IocConfig
{
    public static IServiceCollection Configure(IServiceCollection services, IConfiguration configuration)
    {
         serviceCollection
            .AddDbContext<SomeContext>(options => options.UseSqlServer(configuration["ConnectionString"]));
         serviceCollection.AddScoped<IDepartmentRepository, DepartmentRepository>();
         serviceCollection.AddScoped<IDepartmentAppService, DepartmentAppService>();
         .
         .
         .

         return services;
    }
}

in Startup class and ConfigureServices method just useIocConfig class:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
         IocConfig.Configure(services, configuration);

         services.AddMvc();
         .
         .
         .

if you don't want use IocConfig class, change ConfigureServices in Startup class:

public IServiceCollection ConfigureServices(IServiceCollection services)
{
     .
     .
     .
     return services;

and in test project reuse IocConfig or Startup class:

public class DependencySetupFixture
{
    public DependencySetupFixture()
    {
          var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", false, true));
         configuration = builder.Build();

         var services = new ServiceCollection();

         // services = IocConfig.Configure(services, configuration)
         // or
         // services = new Startup(configuration).ConfigureServices(services);

         ServiceProvider = services.BuildServiceProvider();
    }

    public ServiceProvider ServiceProvider { get; private set; }
}

and in test method:

[Fact]
public async Task Get_DepartmentById_Are_Equal()
{
    using (var scope = _serviceProvider.CreateScope())
    {
        // Arrange
        var departmentAppService = scope.ServiceProvider.GetServices<IDepartmentAppService>();

        // Act
        var departmentDto = await departmentAppService.GetDepartmentById(2);

        // Arrange
        Assert.Equal("123", departmentDto.DepartmentCode);
    }
}

Solution 2

When you are testing. You need to use mocking libraries or Inject your service directly on contructor ie.

public DBContext context;
public IDepartmentAppService departmentAppService;

/// Inject DepartmentAppService here
public DepartmentAppServiceTest(DepartmentAppService departmentAppService)
{
    this.departmentAppService = departmentAppService;
}
Share:
18,477
Admin
Author by

Admin

Updated on June 06, 2022

Comments

  • Admin
    Admin almost 2 years

    I am trying to implement Dependency Injection in Xunit test for AppService. Ideal goal is to run the original application program Startup/configuration, and use any dependency injection that was in Startup, instead of reinitializing all the DI again in my test, thats the whole Goal in question.

    Update: Mohsen's answer is close. Need to update couple syntax/requirement errors to work.

    For some reason, original application works and can call Department App Service. However, it cannot call in Xunit. Finally got Testserver working using Startup and Configuration from original application. Now receiving error below:

    Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService
    
    namespace Testing.IntegrationTests
    {
        public class DepartmentAppServiceTest
        {
            public DBContext context;
            public IDepartmentAppService departmentAppService;
    
            public DepartmentAppServiceTest(IDepartmentAppService departmentAppService)
            {
                this.departmentAppService = departmentAppService;
            }
    
            [Fact]
            public async Task Get_DepartmentById_Are_Equal()
            {
                var options = new DbContextOptionsBuilder<SharedServicesContext>()
                    .UseInMemoryDatabase(databaseName: "TestDatabase")
                    .Options;
                context = new DBContext(options);
    
                TestServer _server = new TestServer(new WebHostBuilder()
                    .UseContentRoot("C:\\OriginalApplication")
                    .UseEnvironment("Development")
                    .UseConfiguration(new ConfigurationBuilder()
                        .SetBasePath("C:\\OriginalApplication")
                        .AddJsonFile("appsettings.json")
                        .Build()).UseStartup<Startup>());
    
                context.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
                context.SaveChanges();
    
                var departmentDto = await departmentAppService.GetDepartmentById(2);
    
                Assert.Equal("123", departmentDto.DepartmentCode);
            }
        }
    }
    

    I am receiving this error:

    Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService
    

    Need to use Dependency injection in testing just like real application. Original application does this. Answers below are not currently sufficient , one uses mocking which is not current goal, other answer uses Controller which bypass question purpose.

    Note: IDepartmentAppService has dependency on IDepartmentRepository which is also injected in Startup class, and Automapper dependencies. This is why calling the whole startup class.

    Good Resources:

    how to unit test asp.net core application with constructor dependency injection

    Dependency injection in Xunit project

  • Admin
    Admin over 4 years
    I cannot test with dependency injection? I can use dependency injection in real application but not with Xunit? Dont want to utilize mock library, this integration test not unit test
  • Yigit Tanriverdi
    Yigit Tanriverdi over 4 years
    The default DI library uses Startup.cs and you can't inject default DI in your test class. Use your test server API endpoints or inject your services on constructor @AlanWalker
  • Yigit Tanriverdi
    Yigit Tanriverdi over 4 years
    You cant test directly. You can test the controllers that DepartmentAppService used in. You should create client to request on your test server @AlanWalker
  • Admin
    Admin over 4 years
    well I will need to test repository, app layers, api layer, so controller test may not only work, thanks though, disappointing Microsoft and Xunit did not create libraries for this yet, I have a whole list of dependency injections I need to run through
  • Admin
    Admin over 4 years
    isnt there a way I can replace DepartmentAppService with a variable name? I dont want to refer to it again, after its declared in startup
  • Admin
    Admin over 4 years
    is it possible to use this? stackoverflow.com/questions/37724738/… answer from Joshua Duxbury trying to get this to work
  • Admin
    Admin over 4 years
    this avoids the question
  • Admin
    Admin over 4 years
    Syntax error: not sure why comments deleted, this line is giving me an error and need be fixed // services = new Startup(configuration).ConfigureServices(services); need hostenvironment name
  • Admin
    Admin over 4 years
    Requirement issue: additionally Configuration builder needs to be set to project directories in question above "C:\\Test\\Test.WebAPI", Not Current Test Directory , new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", false, true)); configuration = builder.Build();
  • Admin
    Admin over 4 years
    hi Yigit answer above, please reedit or feel free to remove answer if needed, thanks
  • Mohsen Esmailpour
    Mohsen Esmailpour over 4 years
    You can load appsetting.josn file by reflection from project directory.
  • Admin
    Admin over 4 years
    ok will research that, spent many hours researching this question, feel free to update answer also, thanks
  • Admin
    Admin over 4 years
    added answer above
  • Admin
    Admin over 4 years
    hi @MohsenEsmailpour as you recommended, I think we should retain "Using DI through Test Fixture" portion, and delete second portion, "Using DI through Startup.cs", unless second portion has any benefits over Custom Web Application Factory, I think web app factory is MS recommended way, plus you recommended it to me! I will send you points for bounty in couple days, thank you! already gave thumbs up
  • O S
    O S over 4 years
    is it possible to do constructor injection, so there is no need to use .GetService?
  • Mohsen Esmailpour
    Mohsen Esmailpour over 4 years
    I don't think so, this kind of test is integration test and basically you should not do dependency in unit test.