在一个微服务环境中,我需要构建一个基于合同的测试框架。我目前正在研究如何将单个服务从它的外部依赖项中分离出来,以便执行提供程序测试。
我需要做的是:
我的解决方案是这样的:
Case-Solution/
├── src/
| ├──Case.Api
| └──Case.Application
├── test/
| ├──Case.Api.Unittest
| ├──(other tests)
| ├──Case.Pact.CunsumerTest
| └──Case.Pact.ProviderTest
我在dotnet中阅读了关于契约测试的这指南。专注于Case.Pace.ProviderTest
,我需要以编程的方式从Case.Pact.ProviderTest
启动Case.Api
(另一个WebHost用于自己约定),并替换其中的一些依赖项。
到目前为止我得到了这个:
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(@"..\..\..\..\..\pacts\caseconsumer-caseprovider.json")
.Verify();
}
#region IDisposable Support
//IDisposable code
#endregion
}
在包含.UseStartup<CaseStartup>()
的行中,我简单地从Case.Api
复制了Startup.cs
并更改了所需的依赖项,这很好。
但我想要一个更通用的解决方案。仅仅复制代码并将其称为“一天”是不对的,因为对于其他服务来说,它并不是通用的和可重用的。
所以我继续挖掘,并想出了以下几点。
从其他程序集添加控制器
我意识到,使用来自不同程序集的IWebhost启动StartUp并不能自动从该程序集中添加控制器。这需要明确地进行。所以我做了这个:
public void ConfigureServices(IServiceCollection services)
{
var assembly = Assembly.Load("Case.Api");
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApplicationPart(assembly)
.AddControllersAsServices();
......
}
棒极了!到目前一切尚好。
下一期:
替换依赖项:
在阅读这文章时,我创建了替换依赖项的扩展方法:
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
的Case.Api
,并以某种方式调整DI以替换依赖项,而不需要所有冗余代码。
任何输入都是非常有用的。
谢谢:)
发布于 2021-09-09 13:38:19
我在使用Pact.net时也遇到过类似的情况。但我想使用TestServer,不幸的是,Pact.net不支持httpClient ( 用内存API验证协议 )。最后,我使用了两个库的组合,可能不是最好的方法来验证所有场景。我使用Pact.net的使用者部分来生成合同,使用触觉的验证者部分来验证提供者是否履行了合同。不过,验证程序需要修改代码才能与Pact.net协议兼容。
我还使用您的代码示例来使用moq替换模拟的依赖项。
[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://learn.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 :这里我使用您的扩展来替换依赖项
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);
}
}
}
}
https://stackoverflow.com/questions/57850859
复制