1

I would like to unit test a async controller. Ive done this many times before but never called the Url.Link()method in order to generate the Location Header.

Heres my demo code.

Controller

public class DemoController : ApiController
{
    [HttpPost]
    public async Task<IHttpActionResult> DemoRequestPost(string someRequest)
    {
        // do something await ...
        var id = 1;
        // generate url for location header (here the problem occurrs)
        var url = Url.Link("DemoRequestGet", new {id = id});
        return Created(url, id);
    }

    [HttpGet]
    [Route("demo/{id}", Name = "DemoRequestGet")]
    public async Task<IHttpActionResult> DemoRequestGet(int id)
    {
        // return something
        return Ok();
    }
}

Test

[TestFixture]
public class DemoControllerTests
{
    [Test]
    public async Task CreateFromDraftShouldSucceed()
    {
        // Arrange
        var request = "Hello World";
        var controller = new DemoController();
        var httpConfiguration = new HttpConfiguration();
        // ensure attribte routing is setup
        httpConfiguration.MapHttpAttributeRoutes();
        httpConfiguration.EnsureInitialized();
        controller.Configuration = httpConfiguration;
        // Act
        var result = await controller.DemoRequestPost(request);
        // Assert
        Assert.AreEqual(result, 1);
    }
}

I am receiving

at NUnit.Framework.Internal.ExceptionHelper.Rethrow(Exception exception) at NUnit.Framework.Internal.AsyncInvocationRegion.AsyncTaskInvocationRegion.WaitForPendingOperationsToComplete(Object invocationResult) at NUnit.Framework.Internal.Commands.TestMethodCommand.RunAsyncTestMethod(TestExecutionContext context)

4
  • You should await the result. Try var result = await controller.DemoRequestPost(request); Commented Jul 19, 2016 at 19:01
  • Sorry, it was a mistake in my sample code. The same Exception occurs if I await the result. Commented Jul 19, 2016 at 19:05
  • 1
    What is the exception that is being thrown and what is the message on the exception? Or is NUnit not reporting the full exception info. If so, that is a bug, please report it at github.com/nunit/nunit/issues. If you are not getting the exception, try wrapping in a try/catch to catch the exception and examine it in the debugger. Hopefully that will tell you why it is failing. Commented Jul 19, 2016 at 19:24
  • Nunit does not report the exception. The test simply failed and even debugging the test does not help. Besides that I guess I think the problem has to do with the missing MapHttpAttributeRoutes. Commented Jul 19, 2016 at 19:38

2 Answers 2

1

Check the Testing Link Generation in Unit Testing Controllers in ASP.NET Web API 2

The UrlHelper class needs the request URL and route data, so the test has to set values for these.

You are not setting those in your example hence your error.

Here is one of their examples

[TestMethod]
public void PostSetsLocationHeader()
{
    // Arrange
    ProductsController controller = new ProductsController(repository);

    controller.Request = new HttpRequestMessage { 
        RequestUri = new Uri("http://localhost/api/products") 
    };
    controller.Configuration = new HttpConfiguration();
    controller.Configuration.Routes.MapHttpRoute(
        name: "DefaultApi", 
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional });

    controller.RequestContext.RouteData = new HttpRouteData(
        route: new HttpRoute(),
        values: new HttpRouteValueDictionary { { "controller", "products" } });

    // Act
    Product product = new Product() { Id = 42, Name = "Product1" };
    var response = controller.Post(product);

    // Assert
    Assert.AreEqual("http://localhost/api/products/42", response.Headers.Location.AbsoluteUri);
}

based on that article you can do something like this which is mocking the UrlHelper using Moq.

[TestClass]
public class DemoControllerTests {
    [TestMethod]
    public async Task CreateFromDraftShouldSucceed() {
        // This version uses a mock UrlHelper.

        // Arrange
        var controller = new DemoController();
        controller.Request = new HttpRequestMessage();
        controller.Configuration = new HttpConfiguration();

        string locationUrl = "http://localhost/api/demo/1";

        // Create the mock and set up the Link method, which is used to create the Location header.
        // The mock version returns a fixed string.
        var mockUrlHelper = new Mock<UrlHelper>();
        mockUrlHelper.Setup(x => x.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(locationUrl);
        controller.Url = mockUrlHelper.Object;

        // Act
        var request = "Hello World";
        var result = await controller.DemoRequestPost(request);
        var response = await result.ExecuteAsync(System.Threading.CancellationToken.None);

        // Assert
        Assert.AreEqual(locationUrl, response.Headers.Location.AbsoluteUri);
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Not bad, do you know a solution like the first one but with Attribute Routing ?
@dknaack, can you elaborate? I don't understand what you mean? Attribute routing would only matter for integration test not unit test. However you can still call mapping extension on the HttpConfiguration when arranging the test
i would like to test the location header url. In order to do that i need the Url Helper to work probably. I can call HttpConfiguration MapHttpAttributeRoutes which works well. But the Url Helper does not generate a url (null).
Check the link I provided. they have examples of how to configure the UrlHelper. it needs a valid request.
Ive checked the link. But the article does not cover AttributeRoutes.
|
0

@dknaack For your controller test, you probably don't need this line of code:

    var httpConfiguration = new HttpConfiguration();
    httpConfiguration.MapHttpAttributeRoutes();
    httpConfiguration.EnsureInitialized();
    controller.Configuration = httpConfiguration;

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.