4

I'm trying to learn webapi and have stumbled across a problem. The training course I was doing showed how to do paging by returning a response header with the next and previous link. However it uses HttpContext.Current.Response.Headers.Add() to send back the next link, previous link, and total pages.

I am also trying to implement unit tests for the controllers. Problem seems to be that the HttpContext.Current is null when running through a unit test. I read somewhere that I shouldn't be HttpContext.Current for webapi as it's not testable, but I'm not sure what I should be using instead.

Here is my contoller code:

public partial class CandidateManagerController
{
    private readonly ICandidateManager _candidateManagerV2;

    public CandidateManagerController(ICandidateManager candidateManager)
    {
        _candidateManagerV2 = candidateManager;
    }

    [VersionedRoute("CandidateManager", 2, Name="CandidateManagerV2")]
    public IHttpActionResult Get(int page = 1, int pageSize = 1)
    {
        try
        {
            var totalCount = 0;
            var totalPages = 0;

            var result = _candidateManagerV2.GetCandidates(out totalCount, out totalPages, page, pageSize);

            var urlHelper = new UrlHelper(Request);


            var prevLink = page > 1
                ? urlHelper.Link("CandidateManagerV2",
                    new
                    {
                        page = page - 1,
                        pageSize = pageSize,
                    })
                : "";


            var nextLink = page < totalPages ? urlHelper.Link("CandidateManagerV2",
                new
                {
                    page = page + 1,
                    pageSize = pageSize
                }) : "";

            var paginationHeader = new
            {
                currentPage = page,
                pageSize = pageSize,
                totalCount = totalCount,
                totalPages = totalPages,
                previousPageLink = prevLink,
                nextPageLink = nextLink
            };

            HttpContext.Current.Response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));



            return Ok(result);
        }
        catch (Exception exp)
        {
            return InternalServerError();
        }
    }

}

Here is my unit test. Please note I'm using Nunit and Moq:

[TestFixture]
public class CandidateManagerControllerV2Tests
{


    [Test]
    [Category("CandidateManagerController Unit Tests")]
    public void Should_Return_List_Of_Candidate_Objects()
    {

        var testList = new List<Candidate>();
        testList.Add(new Candidate() { CandidateId = 1, Name = "Mr", Surname = "Flibble" });
        testList.Add(new Candidate() { CandidateId = 2, Name = "Arnold", Surname = "Rimmer" });

        var totalCount = 0;
        var totalPages = 0;
        var mockManager = new Mock<ICandidateManager>();
        mockManager.Setup(x => x.GetCandidates(out totalCount, out totalPages, It.IsAny<int>(), It.IsAny<int>())).Returns(testList);

        var controller = new CandidateManagerController(mockManager.Object);
        SetupControllerForTests(controller);

        var result = controller.Get(1, 1);
    }

    private static void SetupControllerForTests(ApiController controller)
    {
        var config = new HttpConfiguration();
        var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/candidatemanager");
        var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
        var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });

        controller.ControllerContext = new HttpControllerContext(config, routeData, request);
        controller.Request = request;
        controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
        controller.ActionContext=new HttpActionContext();
    }

}

I'm hoping someone will be able to help me. It could be that I've been led down a wrong path with the way to implement paging. However it is likely that I'd need to add a response header for something any way.

1

2 Answers 2

3

To test your response headers you need to do the following:

  1. Initialize your controller with a ControllerContext in TestInitialize
  2. Call the controller where you add the custom header
  3. Assert it in the TestMethod

It only works if you add your header in the controller class: HttpContext.Response.Headers.Add("x-custom-header", "value");

Example:


public class MyControllerTests
{
    private MyController _controller;
    
    [TestInitialize]
    public void Setup()
    {
        _controller= new MyController();
        _controller.ControllerContext = new ControllerContext()
        {
            HttpContext = new DefaultHttpContext(),
        };
    }
    
    [TestMethod]
    public async Task GetAsyncShouldContainCutomHeader()
    {
        // Act
        await _controller.GetAsync().ConfigureAwait(false);
    
        // Assert
        Assert.IsTrue(_controller.Response.Headers.ContainsKey("x-custom-header"));
        Assert.IsTrue(_controller.Response.Headers["x-custom-header"].Equals("value"));
    }
}

Sign up to request clarification or add additional context in comments.

1 Comment

Saved my day. HttpContext gets null after mocking
2

You should avoid coupling yourself to HttpContext.

Here is another approach to how you can set the header and still be able to unit test it as you intended. You create a HttpResponseMessage, add headers as needed and then create a ResponseMessageResult from it:

//...code removed for brevity

var response = Request.CreateResponse(HttpStatusCode.OK, result);

response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));

IHttpActionResult ok = ResponseMessage(response);

return ok;

You should also note that your controller setup will cause a null reference error when creating your UrlHelper because you are resetting the controller's Request to null when you assign the default ActionContext

private static void SetupControllerForTests(ApiController controller) {
    var config = new HttpConfiguration();
    var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/candidatemanager");
    var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });

    controller.ControllerContext = new HttpControllerContext(config, routeData, request);
    controller.Request = request;
    controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
    //commented this out as it was causing Request to be null
    //controller.ActionContext=new HttpActionContext();
}

The following test passed when checking for the X-Pagination header

public async Task Should_Return_Paged_List_Of_Candidate_Objects() {
    //Arrange
    var testList = new List<Candidate>();
    testList.Add(new Candidate() { CandidateId = 1, Name = "Mr", Surname = "Flibble" });
    testList.Add(new Candidate() { CandidateId = 2, Name = "Arnold", Surname = "Rimmer" });

    var totalCount = 0;
    var totalPages = 0;
    var mockManager = new Mock<ICandidateManager>();
    mockManager.Setup(x => x.GetCandidates(out totalCount, out totalPages, It.IsAny<int>(), It.IsAny<int>())).Returns(testList);

    var controller = new CandidateManagerController(mockManager.Object);
    SetupControllerForTests(controller);

    //Act
    var response = await controller.Get(1, 1).ExecuteAsync(System.Threading.CancellationToken.None);

    //Assert
    Assert.IsNotNull(response);
    Assert.IsInstanceOfType(response, typeof(HttpResponseMessage));
    Assert.IsTrue(response.Headers.Contains("X-Pagination"));
}

4 Comments

how can I test like this, if Request holds a Stream for example? In a multipart context. I would like to like to test like this, but I ended up using in memory HttpServer, so Request is handled like a real world situation
@ntohl just populate the request with what is needed to exercise the system under test. Set the request's Content property accordingly.
I have managed that. Thanks. But after adding CORS to the equality, I had to check headers, if they are generated correctly from [EnableCors("*", "Origin", "POST")], and also I couldn't check what my custom MessageHandler did to the response.
Is config.Routes.MapHttpRoute mandatory if I am using attribute routing instead of conventional routing?

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.