Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test webapi controller with response headers

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.

like image 354
kcis8rm Avatar asked Oct 24 '25 15:10

kcis8rm


2 Answers

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"));
    }
}

like image 121
daniel.szaniszlo Avatar answered Oct 26 '25 04:10

daniel.szaniszlo


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"));
}
like image 33
Nkosi Avatar answered Oct 26 '25 04:10

Nkosi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!