locked
unit tests: testing web api controllers that return Task<HttpResponseMessage> RRS feed

  • Question

  • User1642115476 posted

    Hello,

    I'm trying to write some unit tests for my web API controller and they are not working.

    Here's what I have:

    [TestFixture]
    public class ReportCardControllerSpecs
    {
    public class When_getting_top_3_safeguards : SpecsFor<ReportCardController>
    {
    Task<HttpResponseMessage> results;

    private IEnumerable<CumulativeRiskResult> GetTestSafeguardCriticality(int projectId)
    {
    ...
    }

    protected override void Given()
    {
    ...
    }

    protected override void When()
    {
    results = SUT.GetTop3ReportCard(1);
    }


    [Test]
    public void results_should_have_3_safeguards()
    {

    }
    }
    }

    So as you can see, ReportCardController is the controller class I'm trying to test. GetTestSafeguardCriticality(int) returns my mock data. Given() sets up GetTestSafeguardCriticality(int) to return the mock data at the appropriate points. And When() gets the results to be tested and saves them in results.

    The problem seems to be that results is a Task<HttpResponseMessage> that never enters into a completed state. GetTop3ReportCard() has this signature:

    [Authorize]
    public async Task<HttpResponseMessage> GetTop3ReportCard([FromUri] int projectId)
    {
    if (projectId <= 0)
    {
    return Request.CreateErrorResponse(HttpStatusCode.InternalServerError,
    "Invalid project ID.");
    }

    Top3AndReportCard top3AndReportCard = GetTop3AndReportCard(projectId);

    string jsonString = JsonConvert.SerializeObject(top3AndReportCard);

    return Request.CreateResponse(HttpStatusCode.OK, jsonString);
    }

    I put a breakpoint in When(). Nothing crashes. It goes through fine (except that the status of the Task is "not yet complete"). So I put another breakpoint in results_should_have_3_safeguards(). Again, it comes here but results is still showing a status of "not yet complete").

    Does anyone know how to setup unit tests on controllers that return Task<HttpResponseMessage>?

    Monday, November 14, 2016 10:30 PM

All replies

  • User-2057865890 posted

    Hi Gib9898_00,

    During an asynchronous call, a thread is not blocked from responding to other requests while it waits for the first request to complete.

    unit test sample

    SimpleProductController 

    public class SimpleProductController : ApiController
    {
            List<Product> products = new List<Product>();
    
            public SimpleProductController() { }
    
            public SimpleProductController(List<Product> products)
            {
                this.products = products;
            }
            public async Task<HttpResponseMessage> GetTop3ReportCard(int id)
            {
                await Task.Delay(1000);
                var result = Request.CreateResponse(HttpStatusCode.OK, "test");
                return result;
            }
    }

    TestClass

    [TestClass]
    public class TestSimpleProductController
    {
        [TestMethod]
        public async Task GetTop3ReportCardTest()
        {
            var testProducts = GetTestProducts();
            var controller = new SimpleProductController(testProducts);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();
            var response = await controller.GetTop3ReportCard(4);
            Assert.IsNotNull(response);
        }
        private List<Product> GetTestProducts()
        {
            var testProducts = new List<Product>();
            testProducts.Add(new Product { Id = 1, Name = "Demo1", Price = 1 });
            testProducts.Add(new Product { Id = 2, Name = "Demo2", Price = 3.75M });
            testProducts.Add(new Product { Id = 3, Name = "Demo3", Price = 16.99M });
            testProducts.Add(new Product { Id = 4, Name = "Demo4", Price = 11.00M });
    
            return testProducts;
         }
    }

    reference: https://www.asp.net/web-api/overview/testing-and-debugging/unit-testing-controllers-in-web-api 

    Best Regards,

    Chris

    Tuesday, November 15, 2016 4:56 PM
  • User1642115476 posted

    Thanks for the link, Chris.

    The problem I have with the approach this article gives is that my controller constructors take service interfaces that rely on dependency injection. For example, my ReportCardController looks like this:

    public class ReportCardController : ApiController
    {
    private readonly IPromComService _promComService;
    private readonly IProjectService _projectService;
    private readonly IProjectCriticalityDataService _projectCriticalityDataService;

    public ReportCardController(
    IPromComService promComService,
    IProjectService projectService,
    IProjectCriticalityDataService projectCriticalityDataService)
    {
    _promComService = promComService;
    _projectService = projectService;
    _projectCriticalityDataService = projectCriticalityDataService;
    }

    No where in the code do I manually create these services and pass them into the controller's constructor, and it would not be easy to do so if I tried.

    So while I could overload the constructor such that it takes some mock data (like the examples in the article suggest) instead of services, it needs the services in order to function properly.

    In my application, the controllers are automatically (automagically) created and the services are also automatically created and injected. I initiate the process by a GET or POST request made from the browser, something like:

    http://localhost:60209/api/ReportCard/?projectId=166

    You wouldn't happen to know a way to send off a request like this from inside the unit test, would you?

    Wednesday, November 16, 2016 4:18 PM