I found a solution, but with visual studio 2015, the CTP6 test sometimes passes, sometimes not ... it seems this is a Xunit problem, because when I debug, everything is fine ... but this is not a problem here
link to full code: github repo
here is the middleware that I want to check:
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace Multi.Web.Api { public class MultiMiddleware { private readonly RequestDelegate next; public MultiMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context, IClientProvider provider) { HttpClient calendarClient = null; HttpClient CalcClient = null; try {
Note that the Call method gets an IClientProvider (via DI) that will return another HttpClient object based on some string (this is just for demonstration here). The string may be the name of the provider .... Then we use these clients to call the external apis. This is what I want mock
here is the IClientProvider interface :
using System.Net.Http; namespace Multi.Web.Api { public interface IClientProvider { HttpClient GetClientFor(string providerName); } }
Then I created the middleware (test middleware) to mock the request coming from the SUT (this is above).
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using System; using System.Threading.Tasks; namespace Multi.Web.Api.Test.FakeApi { public class FakeExternalApi { private readonly RequestDelegate next; public FakeExternalApi(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context) { //Mocking the calcapi if (context.Request.Host.Value.Equals("www.calcapi.io")) { if (context.Request.Path.Value == "/count") { await context.Response.WriteAsync("1"); } } //Mocking the calendarapi else if (context.Request.Host.Value.Equals("www.calendarapi.io")) { if (context.Request.Path.Value == "/today") { await context.Response.WriteAsync("2015-04-15"); } else if (context.Request.Path.Value == "/yesterday") { await context.Response.WriteAsync("2015-04-14"); } else if (context.Request.Path.Value == "/tomorow") { await context.Response.WriteAsync("2015-04-16"); } } else { throw new Exception("undefined host : " + context.Request.Host.Value); } await next(context); } } public static class FakeExternalApiExtensions { public static IApplicationBuilder UseFakeExternalApi(this IApplicationBuilder app) { return app.UseMiddleware<FakeExternalApi>(); } } }
here I mock the request coming from two different hosts and listen to a different path. I could also make two intermediate layers, one for each host.
next, I created a TestClientHelper that uses this FakeExternalApi
using Microsoft.AspNet.TestHost; using Multi.Web.Api.Test.FakeApi; using System; using System.Net.Http; namespace Multi.Web.Api { public class TestClientProvider : IClientProvider, IDisposable { TestServer _fakeCalendarServer; TestServer _fakeCalcServer; public TestClientProvider() { _fakeCalendarServer = TestServer.Create(app => { app.UseFakeExternalApi(); }); _fakeCalcServer = TestServer.Create(app => { app.UseFakeExternalApi(); }); } public HttpClient GetClientFor(string providerName) { if (providerName == "calendar") { return _fakeCalendarServer.CreateClient(); } else if (providerName == "calc") { return _fakeCalcServer.CreateClient(); } else { throw new Exception("Unsupported external api"); } } public void Dispose() { _fakeCalendarServer.Dispose(); _fakeCalcServer.Dispose(); } } }
This basically returns the correct client for the server we requested.
Now I can create my test methods:
using System; using System.Net; using System.Threading.Tasks; using Microsoft.AspNet.TestHost; using Microsoft.Framework.DependencyInjection; using Shouldly; using Xunit; using Microsoft.AspNet.Builder; using System.Net.Http; namespace Multi.Web.Api { public class TestServerHelper : IDisposable { public TestServerHelper() { ClientProvider = new TestClientProvider(); ApiServer = TestServer.Create((app) => { app.UseServices(services => { services.AddSingleton<IClientProvider>(s => ClientProvider); }); app.UseMulti(); }); } public TestClientProvider ClientProvider { get; private set; } public TestServer ApiServer { get; private set; } public void Dispose() { ApiServer.Dispose(); ClientProvider.Dispose(); } } public class MultiMiddlewareTest : IClassFixture<TestServerHelper> { TestServerHelper _testServerHelper; public MultiMiddlewareTest(TestServerHelper testServerHelper) { _testServerHelper = testServerHelper; } [Fact] public async Task ShouldReturnToday() { using (HttpClient client = _testServerHelper.ApiServer.CreateClient()) { var response = await client.GetAsync("http://localhost/today"); response.StatusCode.ShouldBe(HttpStatusCode.OK); String content = await response.Content.ReadAsStringAsync(); Assert.Equal(content, "2015-04-15 count is 1"); } } [Fact] public async Task ShouldReturnYesterday() { using (HttpClient client = _testServerHelper.ApiServer.CreateClient()) { var response = await client.GetAsync("http://localhost/yesterday"); response.StatusCode.ShouldBe(HttpStatusCode.OK); String content = await response.Content.ReadAsStringAsync(); Assert.Equal(content, "2015-04-14 count is 1"); } } [Fact] public async Task ShouldReturn404() { using (HttpClient client = _testServerHelper.ApiServer.CreateClient()) { var response = await client.GetAsync("http://localhost/someOtherDay"); response.StatusCode.ShouldBe(HttpStatusCode.NotFound); } } } }
TestServHelper shuts down Api and ClientProvider , which is a mock implementation here, but in production it will be a real ClientProvider implementation that will return the HttpClient target to real hosts. (a factory)
I do not know if this is the best solution, but it fits my needs ... There is still a problem with Xunit.net for the solution ...