本文介绍了如何从另一个程序集中配置ASP.NET核心WebAPI中的服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在微服务环境中,我需要为基于契约的测试构建一个框架。我目前正在研究如何将单个服务与其外部依赖项隔离,以便执行提供程序测试。

我需要做的是:

  • 保持WebApi项目完整
  • 使用一些配置差异启动WepApi实例
  • 锁定选定的依赖项

我的解决方案结构如下:

Case-Solution/
├── src/
|   ├──Case.Api
|   └──Case.Application
├── test/
|   ├──Case.Api.Unittest
|   ├──(other tests)
|   ├──Case.Pact.CunsumerTest
|   └──Case.Pact.ProviderTest

我已经阅读了this关于DotNet中的约定测试的指南。聚焦于Case.Pace.ProviderTest,我需要以编程方式从Case.Pact.ProviderTest(以及Pact It Self的另一个Web主机)和替身启动Case.Api的一些IT依赖项。

到目前为止,我得到的信息是:

public class ProviderApiTests : IDisposable
{
    private string ProviderUri { get; }
    private string PactServiceUri { get; }
    private IWebHost PactServiceWebHost { get; }
    private IWebHost CasesWebHost { get; }
    private ITestOutputHelper OutputHelper { get; }
    public static IConfiguration CaseConfiguration { get; } = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT" ?? "Production"}.json", optional: true)
        .AddEnvironmentVariables()
        .Build();


    public ProviderApiTests(ITestOutputHelper output)
    {
        OutputHelper = output;
        ProviderUri = "http://localhost:9000";
        PactServiceUri = "http://localhost:9001";

        CasesWebHost = WebHost.CreateDefaultBuilder()
            .UseUrls(ProviderUri)
            .UseStartup<CaseStartup>()
            .UseConfiguration(CaseConfiguration)
            .Build();
        CasesWebHost.Start();

        PactServiceWebHost = WebHost.CreateDefaultBuilder()
            .UseUrls(PactServiceUri)
            .UseStartup<ProviderServerStartup>()
            .Build();
        PactServiceWebHost.Start();
    }

    [Fact]
    public void EnsureProviderApiHonoursPactWithConsumer()
    {
        //Arrange
        var config = new PactVerifierConfig
        {
            Outputters = new List<IOutput>
            {
                new XUnitOutput(OutputHelper)
            },
            Verbose = true,
            CustomHeader = new KeyValuePair<string, string>("X-apikey", "XXX")
        };
        //Act //Assert
        IPactVerifier pactVerifier = new PactVerifier(config);
        pactVerifier.ProviderState($"{PactServiceUri}/provider-states")
            .ServiceProvider("CaseProvider", ProviderUri)
            .HonoursPactWith("CaseConsumer")
            .PactUri(@"..........pactscaseconsumer-caseprovider.json")
            .Verify();
    }
    #region IDisposable Support
    //IDisposable code
    #endregion
}

在包含.UseStartup<CaseStartup>()的行中,我只复制了Startup.cs中的Case.Api并更改了所需的依赖项,这很好用。

但我想要一个更通用的解决方案。仅仅复制代码就到此为止是不合适的:),因为它不是泛型的,不能为其他服务重用。

所以我继续挖掘,得出了以下结论。

从其他程序集添加控制器
我意识到,使用来自不同程序集的启动来启动IWebhost并不会自动添加来自该程序集的控制器。这需要明确地完成。所以我这样做了:

public void ConfigureServices(IServiceCollection services)
{
    var assembly = Assembly.Load("Case.Api");
    services.AddMvc()
       .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
       .AddApplicationPart(assembly)
       .AddControllersAsServices();
    ......
}

太棒了!到目前为止还不错。

下一期:

替换依赖项:
阅读this文章,我创建了替换依赖项的扩展方法:

public static void Replace<TRegisteredType>(this IServiceCollection services, TRegisteredType replcement)
{
    for (var i = 0; i < services.Count; i++)
    {
        if (services[i].ServiceType == typeof(TRegisteredType))
        {
            services[i] = new ServiceDescriptor(typeof(TRegisteredType), replcement);
        }
    }
}

这样我就可以替换我想要的依赖项了:(在本例中是QueryHandler)

public void ConfigureServices(IServiceCollection services)
{
    .....
    var queryHandler = Substitute.For<IQueryHandler<Query, QueryResult>>();
    queryHandler.Handle(Arg.Any<Query>()).Returns(new QueryResult(...));
    services.Replace(queryHandler);
    ......
}

但这不能解决我的复制代码问题。

我的梦想是能够使用Case.Api中的Startup.cs,并以某种方式调整DI以替换依赖项,而不需要所有冗余代码。

任何输入都将得到高度重视。
谢谢:)

推荐答案

我使用Pact.net时也遇到过类似的情况。但是我想使用TestServer,不幸的是Pact.net不支持httpClient(Verifying pact with in-memory API)。最后,我使用了两个库的组合,可能不是验证所有场景的最佳方案。我使用Pact.net的消费者部分生成合同,并使用PactifyVerifier部分验证提供商是否遵守合同。但是,验证程序需要修改代码才能与Pact.net协定兼容。

我还使用您的代码示例使用Moq替换了mock的依赖项。

[TestClass]
public class EndpointShouldHonorContract
{

    private HttpClient httpClient;
    private ApiWebApplicationFactory<Startup> testServerFactory;
    Mock<IRepository> repositoryMock =
        new Mock<IRepository>();

    public EndpointShouldHonorContract()
    {
        //omitting code... Creation of mock Data Set https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking?redirectedfrom=MSDN#testing-query-scenarios
        repositoryMock.Setup(s => s.GetQueryable()).Returns(mockDataSet.Object);

        testServerFactory = new ApiWebApplicationFactory<Startup>(services =>
        {
            services.Replace<IRepository>(repositoryMock.Object);
        });

        httpClient = testServerFactory.CreateClient();
    }

    [TestMethod]
    public async Task HonorContract()
    {
          // this is my modified Pactify Verifier
           await MyModifiedPactify
            .VerifyPact
            .PactVerifier
            .Create(httpClient)
            .Between("consumer", "provider")
            //location of contract, there is also an option where you can get contracts from a http location
            .RetrievedFromFile(@"Pacts")
            .VerifyAsync();
    }
 }

Web Api Factory:这里我使用您的扩展替换依赖项

public class ApiWebApplicationFactory<TStartUp>
    : WebApplicationFactory<TStartUp> where TStartUp: class
{

    Action<IServiceCollection> serviceConfiguration { get; }

    public ApiWebApplicationFactory(Action<IServiceCollection> serviceConfiguration) : base()
    {
        this.serviceConfiguration = serviceConfiguration;
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        if (this.serviceConfiguration != null)
        {
            builder.ConfigureServices(this.serviceConfiguration);
        }
    }


}

internal static class ServiceCollectionExtensions
{
    public static void Replace<TRegisteredType>(this IServiceCollection services, TRegisteredType replacement)
    {
        for (var i = 0; i < services.Count; i++)
        {
            if (services[i].ServiceType == typeof(TRegisteredType))
            {
                services[i] = new ServiceDescriptor(typeof(TRegisteredType), replacement);
            }
        }

    }

}

这篇关于如何从另一个程序集中配置ASP.NET核心WebAPI中的服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-19 07:47