Using asp.net 5 TestServer to Mock an external Api call

后端 未结 1 627
悲哀的现实
悲哀的现实 2021-01-25 18:45

I am trying to use TestServer to test my middleware. Somewhere in the middleware, my code is calling an api through HttpClient. I would like to mock this by using a second Test

相关标签:
1条回答
  • 2021-01-25 19:28

    I found a solution, but with visual studio 2015 CTP6 test are sometimes passing, sometimes not... it seems it's an Xunit problem, because when I debug, everything is fine... but that's not the problem here

    link to full code : github repo

    here is the middleware I want to test :

          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
                      {
    
                          //
                          //get the respective client
                          //
                          calendarClient = provider.GetClientFor("calendar");
                          CalcClient = provider.GetClientFor("calc");
    
                          //
                          //call the calendar api
                          //
                          var calendarResponse = "";
                          if (context.Request.Path.Value == "/today")
                          {
                              calendarResponse = await calendarClient.GetStringAsync("http://www.calendarApi.io/today");
                          }
                          else if (context.Request.Path.Value == "/yesterday")
                          {
                              calendarResponse = await calendarClient.GetStringAsync("http://www.calendarApi.io/yesterday");
                          }
                          else
                          {
                              context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                              //does not process further
                              return;
                          }
                          //
                          //call another api
                          //
                          var calcResponse = await CalcClient.GetStringAsync("http://www.calcApi.io/count");
    
                          //
                          // write the final response
                          //
                          await context.Response.WriteAsync(calendarResponse + " count is " + calcResponse);
    
                          await next(context);
                      }
                      finally
                      {
                          if (calendarClient != null)
                          {
                              calendarClient.Dispose();
                          }
                          if (CalcClient != null)
                          {
                              CalcClient.Dispose();
                          }
                      }
    
                  }
              }
    
              public static class MultiMiddlewareExtensions
              {
                  public static IApplicationBuilder UseMulti(this IApplicationBuilder app)
                  {
                      return app.UseMiddleware<MultiMiddleware>();
                  }
              }
          }
    

    Notice that the Invoke method is getting a IClientProvider (through DI) that will return different HttpClient object based on some string (here it's just for the purpose of demo.) the string could be a providerName.... Then we use these clients to call external apis. It's thtis that I want to Mock

    here is the IClientProvider interface:

        using System.Net.Http;
    
        namespace Multi.Web.Api
        {
            public interface IClientProvider
            {
                HttpClient GetClientFor(string providerName);
            }
        }
    

    Then, I've created a middleware (test middleware) to mock the request coming from the SUT (that is here 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 request coming from a two different host and listen to different path. I could do two middleware also, 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();
                }
            }
        }
    

    What it basically does is returning the correct client for the server we asked for.

    Now I can create my Tests 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);
                      }
                  }
    
    
    
    
              }
          }
    

    The TestServHelper wraps up the Api and the ClientProvider which here is a mock implementation, but in production it will be a real ClientProvider implementation that will return HttpClient target to the real Hosts. (a factory)

    I don't know if it's the best solution, but it suits my needs... Still have the problem with Xunit.net to solve...

    0 讨论(0)
提交回复
热议问题