Net Core:在Xunit Test中对AppService、Repository等执行所有依赖注入
Posted
技术标签:
【中文标题】Net Core:在Xunit Test中对AppService、Repository等执行所有依赖注入【英文标题】:Net Core: Execute All Dependency Injection in Xunit Test for AppService, Repository, etc 【发布时间】:2019-12-11 08:55:22 【问题描述】:我正在尝试在 AppService 的 Xunit 测试中实现依赖注入。理想的目标是运行原始应用程序启动/配置,并使用启动中的任何依赖注入,而不是在我的测试中重新初始化所有 DI,这就是整个目标。
更新:Mohsen 的答案很接近。需要更新几个语法/要求错误才能工作。
由于某种原因,原始应用程序可以运行并且可以调用部门应用程序服务。但是,它不能调用 Xunit。最后使用原始应用程序中的启动和配置使测试服务器正常工作。 现在收到以下错误:
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);
我收到此错误:
Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService
需要像实际应用一样在测试中使用依赖注入。 原始应用程序执行此操作。下面的答案目前还不够,一个使用了不是当前目标的模拟,另一个答案使用了绕过问题目的的控制器。
注意:IDepartmentAppService 对 IDepartmentRepository 有依赖关系,IDepartmentRepository 也注入到 Startup 类中,以及 Automapper 依赖关系。这就是调用整个启动类的原因。
好资源:
how to unit test asp.net core application with constructor dependency injection
Dependency injection in Xunit project
【问题讨论】:
您想在控制器级别还是应用服务层进行测试? 你的需求有点混乱,XUnit是一个单元测试框架,它不是一个集成测试框架,你几乎是在尝试用单元测试框架创建一个集成测试,这必然会产生混淆。 DI 将在应用程序中通过解决所有依赖项的入口点发生,而不是通过 xunit 测试用例。您应该在 XUnit 中使用 Mocking 框架。 @user11860043 我在我的旧答案上看到了你的 cmets。正在度假。你的问题现在已经回答了吗?请标记答案,然后请。如果没有,我可以在本周的某个地方查看它。谢谢。 试试 xunit 框架中内置的 xunit di 支持:nuget.org/packages/Xunit.Di,这样您就可以像对任何其他应用程序一样注入服务依赖项。 【参考方案1】:您正在混合单元测试和集成测试。 TestServer
用于集成测试,如果您想重用 Startup
类以避免再次注册依赖项,则应使用 HttpClient
并对使用 IDepartmentAppService
的控制器和操作进行 HTTP 调用。
如果要进行单元测试,则需要设置 DI 并注册所有需要的依赖项来测试 IDepartmentAppService
。
通过测试夹具使用 DI:
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);
在单元测试中使用依赖注入不是一个好主意,你应该避免这种情况。顺便说一句,如果您不想重复注册依赖项,您可以将您的 DI 配置包装在另一个类中,并在任何您想要的地方使用该类。
通过 Startup.cs 使用 DI:
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;
在Startup
类和ConfigureServices
方法中只使用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();
.
.
.
如果您不想使用 IocConfig
类,请将 ConfigureServices
更改为 Startup
类:
public IServiceCollection ConfigureServices(IServiceCollection services)
.
.
.
return services;
并在测试项目中重用 IocConfig
或 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;
在测试方法中:
[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);
【讨论】:
语法错误:不知道为什么删除了 cmets,这行给我一个错误,需要修复 // services = new Startup(configuration).ConfigureServices(services);需要主机环境名称 需求问题:另外需要将配置生成器设置为“C:\\Test\\Test.WebAPI”上方有问题的项目目录,不是当前测试目录,新的 ConfigurationBuilder() .SetBasePath(Directory .GetCurrentDirectory()) .AddJsonFile("appsettings.json", false, true));配置 = builder.Build(); 您可以从项目目录通过反射加载appsetting.josn
文件。
好的会研究一下,花了很多时间研究这个问题,也随时更新答案,谢谢
hi @MohsenEsmailpour 正如你所建议的那样,我认为我们应该保留“通过测试夹具使用 DI”部分,并删除第二部分“通过 Startup.cs 使用 DI”,除非第二部分有任何好处自定义 Web 应用工厂,我认为 Web 应用工厂是 MS 推荐的方式,加上你向我推荐了它!我会在几天内给您发送赏金积分,谢谢!已经竖起大拇指了【参考方案2】:
使用自定义 Web 应用程序工厂和下面的ServiceProvider.GetRequiredService
,随时编辑和优化答案
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);
);
);
集成测试:
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);
资源:
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
【讨论】:
请注意,这会在 .Net core 3+ 上抛出TestServer constructor was not called with a IWebHostBuilder so IWebHost is not available.
【参考方案3】:
当您进行测试时。您需要使用模拟库或直接在构造函数上注入您的服务。
public DBContext context;
public IDepartmentAppService departmentAppService;
/// Inject DepartmentAppService here
public DepartmentAppServiceTest(DepartmentAppService departmentAppService)
this.departmentAppService = departmentAppService;
【讨论】:
我无法使用依赖注入进行测试?我可以在实际应用程序中使用依赖注入,但不能使用 Xunit?不想使用模拟库,这个集成测试不是单元测试 默认 DI 库使用 Startup.cs,您不能在测试类中注入默认 DI。使用您的测试服务器 API 端点或在构造函数 @AlanWalker 上注入您的服务 你不能直接测试。您可以测试 DepartmentAppService 使用的控制器。您应该创建客户端以在您的测试服务器上请求@AlanWalker 好吧,我需要测试存储库、应用程序层、api 层,所以控制器测试可能不仅可以工作,但是谢谢,令人失望的是微软和 Xunit 还没有为此创建库,我有一个完整的列表我需要运行的依赖注入 没有办法可以用变量名替换 DepartmentAppService 吗?在启动时声明后,我不想再次引用它以上是关于Net Core:在Xunit Test中对AppService、Repository等执行所有依赖注入的主要内容,如果未能解决你的问题,请参考以下文章
如何在 xUnit 测试项目(.NET Core)中完成集成测试后关闭 Resharper 测试运行器?
GitHub的dotnet core CI实践(.net core + xUnit + OpenCover + Appveyor + Coveralls.net)