如何使用 xUnit 为使用 Entity Framework 核心和简单注入器构建的 Asp.NetCore WebAPI 构建测试?

How do I build tests using xUnit for an Asp.NetCore WebAPI built with Entity Framework Core and Simple Injector?

我使用 Entity Framework 核心和简单注入器创建了一个 ASP.NET 核心网络 API。

我想使用 xUnit 进行单元测试来测试我的控制器。

我不知道从哪里开始。我相信我必须在我的单元测试中模拟一个容器对象。

这是初始化容器的启动代码:

public class Startup
{
    private Container container;
    public IConfiguration Configuration { get; }
    private IConfigurationRoot configurationRoot;    

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;

        // Build configuration info
        configurationRoot = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .Build();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        InitializeContainer(); 

        services.AddSimpleInjector(container, options =>
        {
            options.AddAspNetCore()
            .AddControllerActivation();
            options.AddLogging();
        });
    }

    private void InitializeContainer()
    {
        container = new SimpleInjector.Container();
        container.Options.ResolveUnregisteredConcreteTypes = false;
        container.ConfigureServices();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseSimpleInjector(container);
        AppSettingsHelper.AppConfig = configurationRoot;
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

这是我的服务安装程序的代码:

public static class ServicesInstaller
{
    public static void ConfigureServices(this Container container)
    {
        container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

        //Assembly.Load will not re-load already loaded Assemblies
        container.Register<IFooContext, FooContext>(Lifestyle.Scoped);
        container.Register<FooContext>(Lifestyle.Scoped);
    }
}

这是一个示例控制器:

[Route("[controller]")]
[ApiController]
public class SomeController : ControllerBase
{
    private readonly ILogger<SomeController> _logger;
    private readonly Container _container;

    public SomeController(ILogger<SomeController> p_Logger, Container p_Container)
    {
        _logger = p_Logger;
        _container = p_Container;
    }
    
    [HttpGet]
    [Route("{p_SomeId}")]
    public Some GetOwnerByOwnerId(Guid p_SomeId)
    {
        Some some;
        using (Scope scope = AsyncScopedLifestyle.BeginScope(_container))
        {
            var dbContext = _container.GetInstance<FooContext>();
            some  = dbContext.Somes.Where(x => x.SomeId == p_SomeId).FirstOrDefault();
        }
        return some;
    }
}

我对使用 SimpleInjector 比较陌生。

我如何模拟用于测试的容器?

所提供示例中的控制器不应耦合到任何特定容器。

明确地将必要的依赖项注入控制器。

[Route("[controller]")]
[ApiController]
public class SomeController : ControllerBase {
    private readonly ILogger<SomeController> _logger;
    private readonly IFooContext dbContext;

    public SomeController(ILogger<SomeController> p_Logger, IFooContext dbContext) {
        _logger = p_Logger;
        this.dbContext = dbContext;
    }
    
    [HttpGet]
    [Route("{p_SomeId}")]
    public Some GetOwnerByOwnerId(Guid p_SomeId) {
        Some some = dbContext.Somes.Where(x => x.SomeId == p_SomeId).FirstOrDefault();
        return some;
    }
}

现在不需要模拟容器,这将被视为实现细节代码味道。

在进行单元测试时模拟依赖抽象并验证预期行为。

控制器也应尽可能精简,因为大多数其他横切关注点(例如确保注入上下文的范围)在启动时由框架通过配置的容器处理。