问题描述
在微服务环境中,我需要为基于契约的测试构建一个框架。我目前正在研究如何将单个服务与其外部依赖项隔离,以便执行提供程序测试。
我需要做的是:
- 保持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的消费者部分生成合同,并使用Pactify的Verifier部分验证提供商是否遵守合同。但是,验证程序需要修改代码才能与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中的服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!