c# - Net Core: Ejecutar todas las inyecciones de dependencia en Xunit Test para AppService, Repository, etc.
.net asp.net-core (3)
Cuando estás probando. Debe usar bibliotecas de imitación o inyectar su servicio directamente en contructor, es decir.
public DBContext context;
public IDepartmentAppService departmentAppService;
/// Inject DepartmentAppService here
public DepartmentAppServiceTest(DepartmentAppService departmentAppService)
{
this.departmentAppService = departmentAppService;
}
Estoy tratando de implementar la inyección de dependencia en la prueba Xunit para AppService. El objetivo ideal es ejecutar la configuración / inicio del programa de aplicación original y usar cualquier inyección de dependencia que estaba en el inicio, en lugar de reiniciar todo el DI nuevamente en mi prueba, ese es el objetivo en cuestión.
Actualización: la respuesta de Mohsen está cerca. Es necesario actualizar los errores de sintaxis / requisito de pareja para que funcionen.
Por alguna razón, la aplicación original funciona y puede llamar al Servicio de aplicaciones del departamento. Sin embargo, no puede llamar a Xunit. Finalmente conseguí que Testserver funcionara usando Inicio y Configuración desde la aplicación original. Ahora recibiendo el error a continuación:
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);
}
}
}
Estoy recibiendo este error:
Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService
Necesita usar la inyección de dependencia en las pruebas como una aplicación real. La aplicación original hace esto. Las respuestas a continuación no son suficientes actualmente, una usa burlas que no es el objetivo actual, otra respuesta usa Controlador que omite el propósito de la pregunta.
Nota: IDepartmentAppService depende de IDepartmentRepository, que también se inyecta en las clases Startup y Automapper. Es por eso que llama a toda la clase de inicio.
Buenos recursos:
cómo probar la aplicación básica asp.net core con inyección de dependencia del constructor
Está mezclando prueba unitaria con prueba de integración.
TestServer
es para prueba de integración y si desea reutilizar la clase de
Startup
para evitar las dependencias de registro nuevamente, debe usar
HttpClient
y hacer una llamada HTTP al controlador y la acción que usa
IDepartmentAppService
.
Si desea hacer una prueba unitaria, debe configurar DI y registrar todas las dependencias necesarias para probar
IDepartmentAppService
.
Usando DI a través del accesorio de prueba:
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);
}
}
}
Usar inyección de dependencia con prueba unitaria no es una buena idea y debe evitarlo. por cierto, si desea no repetir su auto para registrar dependencias, puede ajustar su configuración de DI en otra clase y usar esa clase en cualquier lugar que desee.
Usando DI a través de 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;
}
}
en la clase de
Startup
y el método
ConfigureServices
solo use la clase
IocConfig
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
IocConfig.Configure(services, configuration);
services.AddMvc();
.
.
.
si no desea usar la clase
IocConfig
, cambie
ConfigureServices
en la clase
Startup
:
public IServiceCollection ConfigureServices(IServiceCollection services)
{
.
.
.
return services;
y en el proyecto de prueba reutilice la clase
IocConfig
o
Startup
:
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; }
}
y en método de prueba:
[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);
}
}
Use Custom Web Application Factory y ServiceProvider.ObtengaRequiredService a continuación, no dude en editar y optimizar la respuesta
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);
});
});
}
}
Examen de integración:
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);
}
}
}
Recursos:
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