Unit Testing Async Controller: Exception on Url.Link() - c#

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)

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

#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;

Related

mocking controller context and UrlHelper

inside unit test I'm faking http context using
var fakeHttpContext = new Mock<HttpContextBase>();
var controllerContext = new Mock<ControllerContext>();
controllerContext.Setup(t => t.HttpContext).Returns(fakeHttpContext.Object);
this.controller.ControllerContext = controllerContext.Object;
now inside my code I'm using following
UrlHelper helper = new UrlHelper(this.ControllerContext.RequestContext);
string url = helper.Action("Details", "Pers", new { id = person.Id });
what should I mock inside unit test in order to use UrlHelper code?
Currently UrlHelper helper is null.
p.s. Because of clarity I did not show whole initialization of controller code inside test, it's working, but I'm struggling now with this request context and UrlHelper.
Controller already has a UrlHelper Url property that you can pass an instance or mock. no need to new one up in the code.
Take a look at this example test with a controller that uses the UrlHelper.
[TestClass]
public class UrlHelperTest {
[TestMethod]
public void MockUrlHelper() {
//Arrange
var requestUrl = new Uri("http://myrequesturl");
var request = Mock.Of<HttpRequestBase>();
var requestMock = Mock.Get(request);
requestMock.Setup(m => m.Url).Returns(requestUrl);
var httpcontext = Mock.Of<HttpContextBase>();
var httpcontextSetup = Mock.Get(httpcontext);
httpcontextSetup.Setup(m => m.Request).Returns(request);
var actionName = "MyTargetActionName";
var expectedUrl = "http://myfakeactionurl.com";
var mockUrlHelper = new Mock<UrlHelper>();
mockUrlHelper
.Setup(m => m.Action(actionName, "Register", It.IsAny<object>(), It.IsAny<string>()))
.Returns(expectedUrl)
.Verifiable();
var sut = new MyController();
sut.Url = mockUrlHelper.Object;
sut.ControllerContext = new ControllerContext {
Controller = sut,
HttpContext = httpcontext,
};
//Act
var result = sut.MyAction();
//Assert
mockUrlHelper.Verify();
}
public class MyController : Controller {
[HttpPost]
public ActionResult MyAction() {
var link = GenerateActionLink("MyTargetActionName", string.Empty, string.Empty);
return View((object)link);
}
private string GenerateActionLink(string actionName, string token, string username) {
string validationLink = null;
if (Request.Url != null) {
var encodedToken = EncodedUrlParameter(token);
var url = Url.Action(actionName, "Register", new { Token = encodedToken, Username = username }, Request.Url.Scheme);
validationLink = url;
}
return validationLink;
}
private string EncodedUrlParameter(string token) {
return "Fake encoding";
}
}
}

Unit Testing Asp.Net WebApi Controllers

I am trying to unit test an ApiController and set the current HttpContext, but every time I try and set the request information for the controller HttpContext.Current still seems to be null.
Here is the code:
[TestMethod]
public void MakePayment()
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/payment");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "payment" } });
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
var mockUserManager = new Mock<ApplicationUserManager>(mockUserStore.Object);
mockUserManager.Setup(x => x.GetUser(It.IsAny<string>())).Returns(new ApplicationUser());
IPayPalService payPalService = new PayPalService(mockUserManager.Object);
var paymentController = new PaymentController(payPalService);
paymentController.ControllerContext = new HttpControllerContext(config, routeData, request);
paymentController.Request = request;
paymentController.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
Payment payment = paymentController.DepositMoney(50.00);
Assert.AreEqual(payment.intent, "sale");
}
Is there anything that I am missing here?

Url.Link not working in WebAPI

Using the unit test below .. Iam trying to test my webapi.
[Test]
public void CheckControllerForCreate()
{
var config = new HttpConfiguration();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/product");
var route = config.Routes.MapHttpRoute("Foo", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
var controller = new ProductController
{
ControllerContext = new HttpControllerContext(config, routeData, request),
Request = request,
Url = new UrlHelper(request)
};
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
var result = controller.Create(new Product {Id = 4, Name = "Tomato Soup", Category = "Groceries", Price = 1});
}
[HttpPost]
public HttpResponseMessage Create(Product product)
{
var url = Url;
if (product == null)
throw new HttpResponseException(new HttpResponseMessage{StatusCode = HttpStatusCode.BadRequest,ReasonPhrase = "Product is not specified"});
products.Add(product);
var response = Request.CreateResponse(HttpStatusCode.Created, product);
string uri = Url.Link("Foo", product.Id);
response.Headers.Location = new Uri(uri);
return response;
}
The Create Action throws an exception because uri is null. Now, the Url helper is correctly picking up the RouteName , otherwise there would be a RouteName not found exception. I am assuming that somethign is wrong with my configuration.
I referred to http://www.peterprovost.org/blog/2012/06/16/unit-testing-asp-dot-net-web-api and several other posts for unit testing the controllers.
The WebAPI method is here on codeplex
http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/1acb241299a8#src/System.Web.Http/Routing/UrlHelper.cs
Edit
I have narrowed it down to vpd being null in ( UrlHelper)
IHttpVirtualPathData vpd = configuration.Routes.GetVirtualPath(
request: request,
name: routeName,
values: routeValues);
Can't seem to figure out why ?
You need to set the routeData into the request, the same way you did with the configuration:
controller.Request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
Also you are incorrectly using the Url.Link helper. You haven't specified a controller nor you have indicated the id.
The code in your controller should look like this:
string uri = Url.Link("Foo", new { id = product.Id, controller = "product" });
UPDATE:
Here's a full example.
Controller:
public class ProductController : ApiController
{
public HttpResponseMessage Create(int id)
{
var uri = Url.Link("Foo", new { id = id, controller = "product" });
return Request.CreateResponse(HttpStatusCode.OK, uri);
}
}
Test:
// arrange
var config = new HttpConfiguration();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/product");
var route = config.Routes.MapHttpRoute("Foo", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary(new { controller = "product" }));
var controller = new ProductController
{
ControllerContext = new HttpControllerContext(config, routeData, request),
Request = request,
Url = new UrlHelper(request)
};
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
controller.Request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
// act
var result = controller.Create(4);
// assert
...

Testing WebApi Controller Url.Link

I have the following controller action
public void Post(Dto model)
{
using (var message = new MailMessage())
{
var link = Url.Link("ConfirmAccount", new { model.Id });
message.To.Add(model.ToAddress);
message.IsBodyHtml = true;
message.Body = string.Format(#"<p>Click here to complete your registration.<p><p>You may also copy and paste this link into your browser.</p><p>{0}</p>", link);
MailClient.Send(message);
}
}
To test this I need to setup the controller context
var httpConfiguration = new HttpConfiguration(new HttpRouteCollection { { "ConfirmAccount", new HttpRoute() } });
var httpRouteData = new HttpRouteData(httpConfiguration.Routes.First());
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost");
sut = new TheController
{
ControllerContext = new HttpControllerContext(httpConfiguration, httpRouteData, httpRequestMessage),
MailClient = new SmtpClient { PickupDirectoryLocation = location }
};
This seems like a lot of setup to test the creation of a link. Is there a cleaner way to do this? I have read about in-memory servers but that looks like it applies more to the httpclient than testing the controller directly.
I started using this approach with Web API 2.0.
If you're using a mocking library (and you really should for any real world unit tests), you are able to directly mock the UrlHelper object as all of the methods on it are virtual.
var mock = new Mock<UrlHelper>();
mock.Setup(m => m.Link(It.IsAny<string>(), It.IsAny<object>())).Returns("test url");
var controller = new FooController {
Url = mock.Object
};
This is a far cleaner solution than Ben Foster's answer, as with that approach, you need to add routes to the config for every name that you're using. That could easily change or be a ridiculously large number of routes to set up.
Below is the absolute minimum code required to test UrlHelper without any kind of mocking library. The thing that threw me (and took me some time to track down) was that you need to set the IHttpRouteData of the request. If you don't the IHttpRoute instance will fail to generate a virtual path resulting in an empty URL.
public class FooController : ApiController
{
public string Get()
{
return Url.Link(RouteNames.DefaultRoute, new { controller = "foo", id = "10" });
}
}
[TestFixture]
public class FooControllerTests
{
FooController controller;
[SetUp]
public void SetUp()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost");
request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
request.Properties[HttpPropertyKeys.HttpRouteDataKey] = new HttpRouteData(new HttpRoute());
controller = new FooController
{
Request = request
};
}
[Test]
public void Get_returns_link()
{
Assert.That(controller.Get(), Is.EqualTo("http://localhost/api/foo/10"));
}
}
I'm running into the same idiocy. All the references I can find want you to Mock the Request/Controller, which is (as you pointed out) a lot of work.
Specific references:
http://aspnetwebstack.codeplex.com/discussions/358709/
http://www.peterprovost.org/blog/2012/06/16/unit-testing-asp-dot-net-web-api/#testing-the-harder-stuff-postproduct
ASP.NET MVC Controller Unit Testing - Problem with UrlHelper Extension
ASP.NET MVC: Unit testing controllers that use UrlHelper
the rest of the internet
I haven't gotten around to trying the actual Mocking frameworks, so I have a helper class to "build" my controller. So instead of
sut = new TheController { ... }
I use something like:
// actually rolled together to `sut = MyTestSetup.GetController(method, url)`
sut = new TheController()...
MyTestSetup.FakeRequest(sut, HttpMethod.Whatever, "~/the/expected/url");
For reference, the method is basically:
public void FakeRequest(ApiController controller, HttpMethod method = null, string requestUrl = null, string controllerName = null) {
HttpConfiguration config = new HttpConfiguration();
// rebuild the expected request
var request = new HttpRequestMessage( null == method ? this.requestMethod : method, string.IsNullOrWhiteSpace(requestUrl) ? this.requestUrl : requestUrl);
//var route = System.Web.Routing.RouteTable.Routes["DefaultApi"];
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
// TODO: get from application? maybe like https://stackoverflow.com/a/5943810/1037948
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", string.IsNullOrWhiteSpace(controllerName) ? this.requestController : controllerName } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
// attach fake request
controller.Request = request;
controller.Request.Properties[/* "MS_HttpConfiguration" */ HttpPropertyKeys.HttpConfigurationKey] = config;
}

URL helper method test failing when returning string

I am using ASP.NET MVC 3 and NUnit.
I created a helper method to to return an action method as such (overloaded method):
public static object CategoryIndex(this UrlHelper urlHelper)
{
return new { controller = "Category", action = "Index" };
}
public static string CategoryIndex(this UrlHelper helper, int categoryId)
{
return helper.RouteUrl(new { controller = "Category", action = "Index", id = categoryId });
}
The test that is failing is the second test called CategoryIndex_should_navigate_to_category_index_action_method_with_child_category_id().
private HttpContextBase httpContextBaseStub;
private RequestContext requestContext;
private UrlHelper urlHelper;
[SetUp]
public void SetUp()
{
httpContextBaseStub = MockRepository.GenerateStub<HttpContextBase>();
requestContext = new RequestContext(httpContextBaseStub, new RouteData());
urlHelper = new UrlHelper(requestContext);
}
[Test]
public void CategoryIndex_should_navigate_to_category_index_action_method()
{
// Act
object actual = UrlHelperNavigationExtensions.CategoryIndex(urlHelper);
// Assert
RouteValueDictionary routes = new RouteValueDictionary(actual);
Assert.AreEqual("Category", routes["controller"]);
Assert.AreEqual("Index", routes["action"]);
}
[Test]
public void CategoryIndex_should_navigate_to_category_index_action_method_with_child_category_id()
{
// Arrange
int childCategoryId = 1;
// Act
string actual = UrlHelperNavigationExtensions.CategoryIndex(urlHelper, childCategoryId);
// Assert
Assert.AreEqual("/Category/Index/1", actual);
}
It's complaining that actual is null. Why would this be and how would I rectify it?
it seems to me that you don't initialize the routecollection. I guess something like that would do the trick
[SetUp]
public void SetUp()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
httpContextBaseStub = MockRepository.GenerateStub<HttpContextBase>();
requestContext = new RequestContext(httpContextBaseStub, new RouteData());
//urlHelper = new UrlHelper(requestContext);
urlHelper = new UrlHelper(requestContext, routes);
}
It seems also, that you don't setup the HttpContextBase. Be sure to have it properly mocked or you will get some null reference exception.
[SetUp]
public void SetUp()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
//httpContextBaseStub = (new Moq.Mock<HttpContextBase>()).Object;
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
response.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns((String url) => url);
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Setup(c => c.Request).Returns(request.Object);
mockHttpContext.Setup(c => c.Response).Returns(response.Object);
requestContext = new RequestContext(mockHttpContext.Object, new RouteData());
urlHelper = new UrlHelper(requestContext, routes);
}
My guess is because the route table is empty, so it doesn't know how to generate the url.
Add the normal /{controller}/{action} route and it should work.
(i'm on a phone so forgive if this is wrong)
You need to stub an IRouteHandler implementation, which I've called stub_handler here.
RouteTable.Routes.Add(new Route
(
"{controller}/{action}/{id}"
, stub_handler
);

Categories

Resources