问题
In a microservice environment I need to construct a framework for Contract based testing. I'm currently investigatingh how to isolate a single service from it's external dependencies inorder to execute the Provider tests.
What I need to do is:
- Keep the WebApi project intact
- Start an instance of the WepApi with some config-differences
- Mock out selected dependencies
My Solution structure is this:
Case-Solution/
├── src/
| ├──Case.Api
| └──Case.Application
├── test/
| ├──Case.Api.Unittest
| ├──(other tests)
| ├──Case.Pact.CunsumerTest
| └──Case.Pact.ProviderTest
I've read this guide about Pact Tests in dotnet.
Focusing on Case.Pace.ProviderTest
, I need to start Case.Api
programmatically from Case.Pact.ProviderTest
(and another WebHost for Pact it self) and substitute some of it dependencies.
So far I got this:
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
}
In the line containing .UseStartup<CaseStartup>()
I've simply copied Startup.cs
from Case.Api
and changed the needed dependencies, which works fine.
But I want a more generic solution. It's doesn't feel right to just copy code and call it a day :) because it is not generic and reusable for the other services.
So I kept digging, and came up with the following.
Add Controllers from other assembly
I realised, that starting a IWebhost, using StartUp from a different assembly, doesn't automagically add controller from that assembly. This needs to be done explicitly. So I did this:
public void ConfigureServices(IServiceCollection services)
{
var assembly = Assembly.Load("Case.Api");
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApplicationPart(assembly)
.AddControllersAsServices();
......
}
Awesome!!! So far so good.
Next issue:
Replace dependencies:
Reding this article, I created the extension method for replacing dependencies:
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);
}
}
}
So I'm able to replace the dependencies I want like so: (in this case a QueryHandler)
public void ConfigureServices(IServiceCollection services)
{
.....
var queryHandler = Substitute.For<IQueryHandler<Query, QueryResult>>();
queryHandler.Handle(Arg.Any<Query>()).Returns(new QueryResult(...));
services.Replace(queryHandler);
......
}
But this doesn't solve my issue with copied code.
My wet dream is to be able to use Startup.cs
from Case.Api
and somehow tweak the DI to replace the dependencies, without having all the redundant code.
Any input would be highly appriciated.
Thanks :)
来源:https://stackoverflow.com/questions/57850859/how-to-configureservices-in-asp-net-core-webapi-from-another-assembly