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 depende
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;
}
Use Custom Web Application Factory and ServiceProvider.GetRequiredService
below, feel free to edit and optimize the answer
CustomWebApplicationFactory:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((hostingContext, configurationBuilder) =>
{
var type = typeof(TStartup);
var path = @"C:\\OriginalApplication";
configurationBuilder.AddJsonFile($"{path}\\appsettings.json", optional: true, reloadOnChange: true);
configurationBuilder.AddEnvironmentVariables();
});
// if you want to override Physical database with in-memory database
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddDbContext<ApplicationDBContext>(options =>
{
options.UseInMemoryDatabase("DBInMemoryTest");
options.UseInternalServiceProvider(serviceProvider);
});
});
}
}
Integration Test:
public class DepartmentAppServiceTest : IClassFixture<CustomWebApplicationFactory<OriginalApplication.Startup>>
{
public CustomWebApplicationFactory<OriginalApplication.Startup> _factory;
public DepartmentAppServiceTest(CustomWebApplicationFactory<OriginalApplication.Startup> factory)
{
_factory = factory;
_factory.CreateClient();
}
[Fact]
public async Task ValidateDepartmentAppService()
{
using (var scope = _factory.Server.Host.Services.CreateScope())
{
var departmentAppService = scope.ServiceProvider.GetRequiredService<IDepartmentAppService>();
var dbtest = scope.ServiceProvider.GetRequiredService<ApplicationDBContext>();
dbtest.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
dbtest.SaveChanges();
var departmentDto = await departmentAppService.GetDepartmentById(2);
Assert.Equal("123", departmentDto.DepartmentCode);
}
}
}
Resources:
https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2
https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api
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);
}
}