0

Please see the code below. Using in-memory hosting of httpclient, and Passing httpclient object to controller in order to unit test action method. But I am getting "Internal Server Error" ReasonPhrase upon HttpResponseMessage response =_httpClient.GetAsync. Please help me, is it correct approach?

    private readonly HttpClient _httpClient;
    public SecurityMfMvcController(HttpClient httpClient)
    {
        this._httpClient = httpClient;
    }
    [HttpGet]
    public ActionResult GetSecuritiesMfs()
    {
        try
        {                
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response =
                _httpClient.GetAsync(ConfigurationManager.AppSettings["ApiUrl"] + "SecuritiesWebApiMf").Result;
            response.EnsureSuccessStatusCode();
            List<SecurityMutualFundDto> list =
            response.Content.ReadAsAsync<List<SecurityMutualFundDto>>().Result;
            return View("SecuritiesMf", list);

        }
        catch (Exception ex)
        {
            return View("Error", ex.Message);
        }

    }
//Unit Test Method for this Action
 [Test]
    public void TestActionGetSecuritiesMfs()
    {
        var config = new HttpConfiguration()
        {
            IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
        };
        //use the configuration that the web application has defined
        WebApiConfig.Register(config);
        HttpServer server = new HttpServer(config);
        //create a client with a handler which makes sure to exercise the formatters
        using (var client = new HttpClient(new InMemoryHttpContentSerializationHandler(server)))
        {
            System.Uri uri = new System.Uri("http://localhost:55893/api/");
            client.BaseAddress = uri;
            var controller = new SecurityMfMvcController(client);
            var result = controller.GetSecuritiesMfs();
            Assert.IsNotNull(result);
        }

    }
//MessageHandler
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
    public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    {
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Content = await ConvertToStreamContentAsync(request.Content);

        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        response.Content = await ConvertToStreamContentAsync(response.Content);

        return response;
    }

    private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
    {
        if (originalContent == null)
        {
            return null;
        }

        StreamContent streamContent = originalContent as StreamContent;

        if (streamContent != null)
        {
            return streamContent;
        }

        MemoryStream ms = new MemoryStream();

        await originalContent.CopyToAsync(ms);

        // Reset the stream position back to 0 as in the previous CopyToAsync() call,
        // a formatter for example, could have made the position to be at the end
        ms.Position = 0;

        streamContent = new StreamContent(ms);

        // copy headers from the original content
        foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
        {
            streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        return streamContent;
    }
}
1
  • 1
    Modified the original question. I have pasted all the code required to give complete scenario. Commented Feb 10, 2015 at 16:18

2 Answers 2

1

You could mock your http request pipeline and test your action:

  var mockHttpRequest = new Mock<HttpRequestMessage>(new object[] {new HttpMethod("GET"), "www.someuri.com"});
  var mockHttpConfig = new Mock<HttpConfiguration>();
  var mockRouteData = new Mock<IHttpRouteData>();

  var mockHttpContext =
    new Mock<HttpControllerContext>(new object[]
                                      {mockHttpConfig.Object, mockRouteData.Object, mockHttpRequest.Object});

Then set your controller object with these values:

var controller = new YourController();
controller.ControllerContext = mockHttpContext.Object;
controller.Request = controller.ControllerContext.Request;
response = controller.SecuritiesMF();

and you could check your response as follows:

Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Sign up to request clarification or add additional context in comments.

7 Comments

you had working example of doing this? I am getting error at controller.Request = controller.ControllerContext.Request; Also please review the code I have edited above?
is your mockHttpContext mocking a HttpControllerContext from the System.Web.Http.Controllers namespace (located in System.Web.Http.dll)? The code i wrote ought to work.
I am taking HttpClient object into MVCController constructor, actually I am injecting it through unity container. So for unit test, I wanted to Mock this, please let me know if you think its not the right approach.
I also tried without passing into constructor (newing it up, without injecting), and I tired the way you suggested - Giving me error at ' controller.ControllerContext = mockHttpContext.Object;' the Error was: Cannot convert source type 'System.Web.Http.Controllers.HttpControllerContext' to target type 'System.Web.Mvc.ControllerContext'
yes, I have references for using System.Web.Http; using System.Web.Http.Controllers; Giving me error at ' controller.ControllerContext = mockHttpContext.Object;' the Error was: Cannot convert source type 'System.Web.Http.Controllers.HttpControllerContext' to target type 'System.Web.Mvc.ControllerContext'
|
1

I got it working, correct me in case of anything wrong here. I have to create a "FakeHttpMessageHandler" as below and the content type to match System.Net.Http.StreamContent for application/json content type. the below code is working to unit test mvc action method using httpclient to call WebAPI. however I need to double check whether this is the right approach for unit test, will review further.

[Test]
    public void TestActionMethodSelectByIdUsingFakeHandler()
    {
        var uobj = new UnitTestForApiController();
        var testobj= uobj.GetsecuritiesMfsList();
        MemoryStream stream = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, testobj);
        var response = new HttpResponseMessage(HttpStatusCode.OK) 
        {Content = new StreamContent(stream)};

        using (var httpClient = new HttpClient(new FakeHandler
        {
            Response = response,
            InnerHandler = new HttpClientHandler()
        }))
        {
            System.Uri uri = new System.Uri("http://localhost:55893/api/");
            httpClient.BaseAddress = uri;
            var controller = new SecuritiesMfMvcController(httpClient);
            var result = controller.Select(2155) as ViewResult;
            Assert.IsNotNull(result);
            Assert.AreEqual(result.ViewName,"Select");
            Assert.AreEqual(result.Model, testobj.FirstOrDefault());

        }

//FakeHandler class goes as below

public class FakeHandler : DelegatingHandler
{
    public HttpResponseMessage Response { get; set; }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => Response, cancellationToken);
    }
}

//We can also have logic for abstracting appropriate StreamContent Creation into FakeContent class like below:
public class FakeHttpContent : HttpContent
{
    public object Content { get; set; }

    public FakeHttpContent(object content)
    {
        Content = content;
    }

    protected async override Task SerializeToStreamAsync(Stream stream,
        TransportContext context)
    {
        MemoryStream ms = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(ms, Content);
        await ms.CopyToAsync(stream);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = Content.ToString().Length;
        return true;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.