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;
}
Related
I'm creating a unit test using nunit and all of this code works fine in runtime.
I have this protected HttpResponseMessage code below that is being called by my controller when it returns.
However, an error:
"Value cannot be null. Parameter name: request" is displaying.
And when I check the request, it is actually null.
Question:
How will I code my unit test to return the HttpResponseMessage?
Error is shown in this line:
protected HttpResponseMessage Created<T>(T result) => Request.CreateResponse(HttpStatusCode.Created, Envelope.Ok(result));
Here is my Controller:
[Route("employees")]
[HttpPost]
public HttpResponseMessage CreateEmployee([FromBody] CreateEmployeeModel model)
{
//**Some code here**//
return Created(new EmployeeModel
{
EmployeeId = employee.Id,
CustomerId = employee.CustomerId,
UserId = employee.UserId,
FirstName = employee.User.FirstName,
LastName = employee.User.LastName,
Email = employee.User.Email,
MobileNumber = employee.MobileNumber,
IsPrimaryContact = employee.IsPrimaryContact,
OnlineRoleId = RoleManager.GetOnlineRole(employee.CustomerId, employee.UserId).Id,
HasMultipleCompanies = EmployeeManager.HasMultipleCompanies(employee.UserId)
});
}
The reason why you are getting:
An exception of type 'System.ArgumentNullException' occurred in System.Web.Http.dll but was not handled in user code
Additional information: Value cannot be null.
is because the Request object is null.
The solution for that is to create an instance of your controller in your tests such as:
var myApiController = new MyApiController
{
Request = new System.Net.Http.HttpRequestMessage(),
Configuration = new HttpConfiguration()
};
In this way, when creating a new instance of the MyApiController class we are initializing the Request object. Moreover, it is also necessary to provide the associated configuration object.
Finally, an example of Unit Test for your Api Controller could be:
[TestClass]
public class MyApiControllerTests
{
[TestMethod]
public void CreateEmployee_Returns_HttpStatusCode_Created()
{
// Arrange
var controller = new MyApiController
{
Request = new System.Net.Http.HttpRequestMessage(),
Configuration = new HttpConfiguration()
};
var employee = new CreateEmployeeModel
{
Id = 1
};
// Act
var response = controller.CreateEmployee(employee);
// Assert
Assert.AreEqual(response.StatusCode, HttpStatusCode.Created);
}
}
I think what happens is that you are not instantiating or assigning your Request property (HttpRequestMessage) when you new up your Controller. I believe it's mandatory to specify the request prior calling into the Api method via your unit test.
You may also require a Configuration (HttpConfiguration):
sut = new YourController()
{
Request = new HttpRequestMessage {
RequestUri = new Uri("http://www.unittests.com") },
Configuration = new HttpConfiguration()
};
Let me know if that works.
Also, if your controller has injections, you can do:
var controller= new MyController(injectionA, injectionB, injectionC)
{
Request = new HttpRequestMessage(),
Configuration = new HttpConfiguration()
};
I find them all on the easy to understand official doc now.
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;
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?
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
...
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
);