在API中,使用一个参数创建多个控制器构造函数

时间:2018-06-25 12:59:59

标签: asp.net-core-webapi

[Route("api/[controller]")]
    public class DigitalDocumentController : Controller
    {

        private IDigitalDocumentService digitalDocumentService;
        private IDatabaseInitializer databaseInitializer;

        public DigitalDocumentController(IDigitalDocumentService digitalDocumentService)
        {
            this.digitalDocumentService = digitalDocumentService;
        }

        public DigitalDocumentController(IDatabaseInitializer databaseInitializer)
        {
            this.databaseInitializer = databaseInitializer;
        }

我希望我的项目中的两个控制器构造器能够在xUnit Testing中模拟,但是我的招摇界面中有一个错误{ “错误”:“在类型'i2ana.Web.Controllers.DigitalDocumentController'中找到了接受所有给定参数类型的多个构造函数。应该只有一个适用的构造函数。 } 有人可以帮我怎么做吗?

... 我想做的是在数据库中测试名称字段的唯一性 我的测试代码:

  

[事实]           公共无效AddNotUniqueName_ReturnsNotFoundObjectResult()           {               var digitalDocument =新的DigitalDocument               {                   图片=新字节[] {0x20、0x20、0x20、0x20、0x20、0x20、0x20},                   CreatedOn = DateTime.Today,                   ID = 6                   位置=“温度”,                   名称=“花”,                   Tages =新列表{新标签{ID = 1,值=“标签1”},新标签{ID = 1,值=“标签2”}}               };               //安排               var mockRepo = new Mock();               mockRepo.Setup(repo => repo.SeedAsync())。Returns(Task.FromResult(AddUniqueDigitalDocument(digitalDocument)));               var controller = new DigitalDocumentController(mockRepo.Object);

        // Act
        var result = controller.Add(digitalDocument);

        // Assert
        var viewResult = Assert.IsType<NotFoundObjectResult>(result);
        var model = Assert.IsAssignableFrom<int>(viewResult.Value);
        Assert.NotEqual(6, model);
    }

“ AddUniqueDigitalDocument”仅返回6以测试新的数字文档与我的初始化数据ID是否不同。

1 个答案:

答案 0 :(得分:1)

使用依赖项注入时,您应该只有一个可以满足所有依赖项的构造函数。否则,DI容器如何知道要使用哪个构造函数?这是您的问题。使用Microsoft.Extensions.DependencyInjection程序包,并且由于这是您要注入的控制器,因此只有一种合理的方法可以解决此问题:不要注册IDigitalDocumentService或{{ 1}}。如果仅注册了一个,则服务集合将仅使用其已注册服务的构造函数。

使用功能更强大的DI容器是可能的,您可以配置一些内容以允许其选择适当的构造函数。但是,如何执行将完全取决于最终要使用的DI容器,因此在这一点上还不能说太多。只需意识到默认容器(Microsoft.Extensions.DependencyInjection)是有意简化的,所以如果您需要更复杂,则应将其放入一个完整的DI容器中。

更新

您应该使用测试主机和内存数据库进行集成测试。基本方法是:

IDatatabaseInitializer

在您应用的public MyTests() { _server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>()); _context = _server.Host.Services.GetRequiredService<MyContext>(); _client = _server.CreateClient(); } 中,创建一个虚拟方法:

Startup

然后,在public virtual void ConfigureDatabase(IServiceCollection services) { // normal database setup here, e.g. services.AddDbContext<MyContext>(o => o.UseSqlServer(Configuration.GetConnectionString("Foo"))); } 中,用对此方法的调用替换您的数据库设置。

最后,在测试项目中,创建一个ConfigureServices类并覆盖TestStartup方法:

ConfigureDatabase

现在,在测试中,您仅向测试客户端发出请求(这只是一个public class TestStartup : Startup { public override void ConfigureDatabase(IServiceCollection services) { var databaseName = Guid.NewGuid().ToString(); services.AddDbContext<MyContext>(o => o.UseInMemoryDatabase(databaseName)); } } 实例,因此它的工作原理与其他HttpClient一样)。首先,使用适当的测试数据设置数据库,然后确保返回正确的响应:

HttpClient

使用API​​显然要容易得多,就像使用Web应用程序一样,您总是需要进行一些HTML解析。但是,docs和相应的sample app可以帮助您。

此外,在实际操作中,您还希望使用测试夹具来避免必须为每个测试引导测试服务器。同样,文档涵盖了您。但是要注意的一件事是,一旦您切换到使用固定装置,您的数据库将在测试之间保持不变。要隔离测试数据,请确保在每次测试之前在上下文中调用// Arrange _context.Add(new DigitalDocument { Name = "Foo" }); await _context.SaveChanges(); // Act // Submit a `DigitalDocument` with the same name via `_client` // Assert // Inspect the response body for some indication that it was considered invalid. Or you could simply assert that no new `DigitalDocument` was created by querying `_context` (or both) 。这可以在测试类的构造函数中轻松完成:

EnsureDeleted()

但是,我什至在测试中都不喜欢这么多的自举代码,所以我通常改为继承自Fixture类:

public class MyTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly HttpClient _client;
    private readonly MyContext _context;

    public MyTests(WebApplicationFactory<Startup> factory)
    {
        factory = factory.WithWebHostBuilder(builder => builder.UseStartup<TestStartup>());
        _client = factory.CreateClient();
        _context = factory.Server.Host.Services.GetRequiredService<MyContext>();
        _context.EnsureDeleted();
     }

然后,对于每个测试类别:

public class TestServerFixture : IClassFixture<WebApplicationFactory<Startup>>
{
    protected readonly HttpClient _client;
    protected readonly MyContext _context;

    public TestServerFixture(WebApplicationFactory<Startup> factory)
    {
        factory = factory.WithWebHostBuilder(builder => builder.UseStartup<TestStartup>());
        _client = factory.CreateClient();
        _context = factory.Server.Host.Services.GetRequiredService<MyContext>();
        _context.EnsureDeleted();
     }
}

这似乎很多,但其中大多数是一次性设置。这样,您的测试将在许多方面变得更加准确,更加健壮,甚至更加轻松。