1

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 TestServer but wonder if this is possible. I already tried, but I have the error : "Trying to connect ... but the server actively refused it."

here is how the code could look like :

  [Fact]
    public async Task Should_give_200_Response()
    {
        var server = TestServer.Create((app) => 
        {
            app.UseMiddleware<DummyMiddleware>();
        });
        var fakeServer = TestServer.Create((app) => 
        {
            app.UseMiddleware<FakeMiddleware>();
        });

        using(server)
        {
          using(fakeServer)
          { 
            var response = await server.CreateClient().GetAsync("/somePath");
            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
          }
        }
    }

Somewhere in the code of DummyMiddleware, I am doing an

 HttpClient client = new HttpClient() { BaseAddress = "http://fakeUrl.com"};
 var resp = client.GetAsync("/path/to/api");

the url : http://fakeUrl.com would be mocked by the fakeServer. but in fact a real call to http://fakeUrl is issued over the network, but I would like it to hit the fakeserver so that I could mock that api.

Imagine that the fakeserver would mock google calendar api for instance.

Update : Use of fakeServer In fact the fakeServer would listen to this url : "http://fakeUrl.com" and when receive the route "/path/to/api" it would return a json object for instance. What I would like to, is my fakeServer to be returned a mocked object. as a reminder, "http://fakeUrl.com/path/to/api" would be called somewhere in my code with an HttpClient object.

Cedric Dumont
  • 1,009
  • 17
  • 38
  • I don't understand your test and where the `fakeServer`is supposed to mock your dummy code, you don't use the `fakeServer` instance. – agua from mars Apr 14 '15 at 08:57
  • Hi I updated the question to add more information. I don't use the fakeServer instance. in fact I would like it to listen to the "http://fakeUrl.com" url – Cedric Dumont Apr 14 '15 at 09:18
  • 1
    so you need a server that return a json object and the ability to start it in your tests, right ? – agua from mars Apr 14 '15 at 12:22
  • yes like the TestServer (in memory) no other installation and better not really starting as I could use a CI.... with one server it's ok, but I don't know how to configure the second. it seems I need to write a special messageHandler and pass it to the HttpClient object – Cedric Dumont Apr 14 '15 at 16:16
  • 1
    Why don't you mock HttpClient ? – agua from mars Apr 15 '15 at 07:26

1 Answers1

1

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...

Cedric Dumont
  • 1,009
  • 17
  • 38