Goal: test that a given url returns a given controller function.
In the process, I've broken into the routing system and I can't figure out how to test routes (or, for that matter, find the controller corresponding to the route :-/).
Sample code, which doesn't work:
[Test]
public void kick_the_tires()
{
var rc = new RouteCollection();
Infrastructure.RouteRegistry.RegisterRoutes(rc);
// get the route corresponding to name.
var got = rc["name"];
var expected = //What? foo is an internal type that can't be instantiated.
Assert.AreEqual(foo, frob);
}
edit: Using the linked blog post from Simon for the stub class.
[TestCase("/", "~/", "Home", "Index")]
[TestCase("/", "api/command", "Other", "command")]
internal void stub_mocker(string apppath, string route, string expected_controller,\
string expected_action)
{
var rc = new RouteCollection();
Infrastructure.RouteRegistry.RegisterRoutes(rc);
var httpmock = new StubHttpContextForRouting(
appPath: apppath,
requestUrl: route);
// this always returns null for everything but the Index case.
var routeData = rc.GetRouteData(httpmock);
var controller = routeData.Values["controller"];
var action = routeData.Values["action"];
Assert.AreEqual(expected_controller, controller);
Assert.AreEqual(expected_action, action);
}
All you are testing right now is if the routes are added to the collection, by accessing it by the route name, and not if the expected route will return given a virtual path. You need to obtain the route data as returned by the RouteCollection with a HttpContext.
Best way would be to use a mock or a stub for the HttpContext (or HttpContextBase) and call the RouteCollection's GetRouteData(HttpContextBase) method and inspect the route data.
There is a good example of this in Brad Wilson's blog:
http://bradwilson.typepad.com/blog/2010/07/testing-routing-and-url-generation-in-aspnet-mvc.html
Edit:You cannot get a controller instance from the RouteData itself. However, RouteData should give you enough information to know which controller will be instantiated. For example, if you have a controller at MyProject.Controllers.HomeController with an action Home, this should hold true in your test (using xUnit and Moq):
// Prepare
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
context.SetupGet(c => c.Request).Returns(request.Object);
context.SetupGet(c => c.Response).Returns(response.Object);
context.SetupGet(c => c.Session).Returns(session.Object);
context.SetupGet(c => c.Server).Returns(server.Object);
request.SetupGet(r => r.HttpMethod).Returns("GET");
request.SetupGet(r => r.PathInfo).Returns(String.Empty);
request.SetupGet(r => r.AppRelativeCurrentExecutionFilePath).Returns("~/Home");
var expectedHandler = typeof (HomeController).GetMethod("Index", Type.EmptyTypes);
var data = RouteTable.Routes.GetRouteData(context.Object);
Assert.NotNull(data);
var handler = (MethodInfo) data.DataTokens["actionMethod"];
Assert.Equal(expectedHandler, handler);
I've had prety good experience with MVCContrib's Testhelper
Take a look at this test of testhelper.
Saves a lot of hassles around stubbing HttpContext etc.
Also if you are on MVC4, have a look at this Nuget package which is a fork for MVC4.
Related
I have the following code that I need to unit test:
public ActionResult VerifyVoucherCode()
{
var model = new VerifyVoucherModel();
model.Voucher = Request.GetFirstQueryValue("token", "voucher");
The second line here is where my test fails. The code for this method is:
public static string GetFirstQueryValue(this HttpRequestBase request, params string[] keys)
{
return request.QueryString.GetFirstValue(keys);
}
My attempt to unit test this part of the method so far is:
var httpContext = CreateHttpContext();
var httpRequestBase = new HttpRequestWrapper(httpContext.Request);
var nameValueCollection = new NameValueCollection();
var controller = CreateMvcController<SignUpController>();
MockActivationCodeHelper(validationResult, controller);
For reference, the CreateHttpContext method is:
private HttpContext CreateHttpContext()
{
var httpContext = new HttpContext(
new HttpRequest("", "http://tempuri.org", "token=123"),
new HttpResponse(new StringWriter())
);
return httpContext;
}
It fails here return request.QueryString.GetFirstValue(keys); with a System Not Implemented exception for Query String. Any pointers here please?
Please take a look at flurl, while it may require some refactoring to your code, using it allows to construct http requests in a very handy and fluent way. Moreover, it makes testing your business logic that depends on calling external HTTP services extremely easy (event for edge cases)
The library has good documentation and his author is very responsive on stackoverflow.
Check out: https://flurl.dev/
I have a controller method in my webapi project which calls a service method and converts the response of the service method into a Dto object for sending the response.
The controller looks something like this:
[HttpPost]
[Route(WebApiConfig.RootApiUri + "/v1/examplepost")]
public async Task<List<Html1Dto>> examplepost([FromBody] SomeInfoDto someInfoDto)
{
var someInfo = _mapper.Map<SomeInfoDto, SomeInfo>(someInfoDto);
return this._mapper.Map<List<Html1>, List<Html1Dto>>(await this._someService.SomeCall(someInfo));
}
and the mock test like this :
//Arrange
var mockMapper = new Mock<IMapper>();
var mockSomeService = new Mock<ISomeService<Html1>>();
mockSomeService.Setup(s => s.SomeCall(It.IsAny<SomeInfo>())).ReturnsAsync(
new List<Html1>() {new Html1() {....}});
SomeInfoDto mockSomeInfoDto = new SomeInfoDto()
{
..
};
SomeInfo mockSomeInfo = new SomeInfo();
mockMapper.Setup(m => m.Map<SomeInfoDto, SomeInfo>(mockSomeInfoDto))
.Returns(mockSomeInfo);
mockMapper.Setup(m => m.Map<List<Html1>, List<Html1Dto>>(It.IsAny<List<Html1>>())).Returns(It.IsAny<List<Html1Dto>>);
var someController = GetController(mockMapper.Object, mockSomeService.Object);
//Act
var result = await someController.examplePost(mockSomeInfoDto);
I am using automapper to map the objects with Dtos. When I debug this test, result comes as null. The mapping of incoming dto works fine. I suspect there is some issue with the service method setup. Anyhelp is appreciated.
Your mapper mock is the other way round
mockMapper.Setup(m => m.Map<List<Html1>, List<Html1Dto>>(It.IsAny<List<Html1>>())).Returns(It.IsAny<List<Html1Dto>>);
to the signature in the method
this._mapper.Map<List<Html1Dto>, List<Html1>>(await this._someService.SomeCall(someInfo));
Additionally, assuming that is correct in your actual code, then the other bit that could be causing you issue is that the return It.IsAny<List<Html1Dto>> which will be null as default(List<HtmlDto>) is null, return a concrete class there instead as below.
This call:
this._mapper.Map<List<Html1Dto>, List<Html1>>(await this._someService.SomeCall(someInfo));
Doesn't have a setup in the Unit Test, so will return null. You need to arrange that to, probably something like:
mockMapper.Setup(m => m.Map<List<Html1Dto>, List<Html1>>(It.IsAny<List<Html1>>()))
.ReturnsAsync(new List<Html1Dto> { ... });
I don't understand how you would test something like the following controller.
How would you mock Request? It seems to me that to mock it you would need to pass in a Request to the method but that is wrong. Or you would need to inject it into the Controller constructor but that seems wrong too.
I totally get how this would work with ISomethingService or ISomethingRepository but for intrinsic dependancies I just don't get it.
Thanks
public ActionResult Test()
{
return View(Request.Browser.Crawler ? "A" : "B");
}
you need to create a mock http context. there are multiple libraries to do so, but you basically need to do something like this:
var request = new HttpRequest("", "http://localhost/", "");
var writer = new StringWriter();
var response = new HttpResponse(writer);
var context = new HttpContext(request, response);
I'm creating a really simple ViewResult subclass called JavaScriptViewResult that, when executing, calls the base implementation and then sets the Content-Type of the response to text/javascript. In trying to unit test this class, I'm running across a slew of difficulties fulfilling all of the dependencies of the ASP.NET MVC stack.
Here is what my unit test, which uses Rhino, looks like so far:
[TestMethod]
public void TestExecuteAction()
{
var request = MockRepository.GenerateMock<HttpRequestBase>();
request.Expect(m => m.Url).Return(new Uri("/Test/JavaScript", UriKind.Relative));
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
httpContext.Expect(m => m.Request).Return(request);
var controller = MockRepository.GenerateMock<ControllerBase>();
var virtualPathProvider = MockRepository.GenerateMock<VirtualPathProvider>();
var routeCollection = new RouteCollection(virtualPathProvider);
routeCollection.MapRoute("FakeRoute", "Test/JavaScript", new { controller = "Test", action = "JavaScript" });
var routeData = routeCollection.GetRouteData(httpContext);
var context = new ControllerContext(httpContext, routeData, controller);
var viewResult = new JavaScriptViewResult();
viewResult.ExecuteResult(context);
Assert.AreEqual("text/javascript", context.HttpContext.Response.ContentType);
}
The latest exception when running the test is a NullReferenceException deep within the bowels of System.Web.Routing.Route.GetRouteData(HttpContextBase httpContext).
How do I set up all of the dependencies for executing a ViewResult? Are there any techniques for making this simpler? Alternately, is there a different way I can utilize the MVC view engine to generate JavaScript that will set the proper Content-Type for the response?
I figured out how to meet the minimum requirements of ViewResult. One problem I was encountering was mocking the process of finding the view. This was avoidable by ensuring that the View property of my object was populated. Here is my working test:
[TestMethod]
public void TestExecuteAction()
{
var response = MockRepository.GenerateStub<HttpResponseBase>();
response.Output = new StringWriter();
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
httpContext.Expect(m => m.Response).Return(response);
var routeData = new RouteData();
routeData.Values.Add("action", "FakeAction");
var context = new ControllerContext(httpContext, routeData, MockRepository.GenerateMock<ControllerBase>());
var viewResult = new JavaScriptViewResult();
viewResult.View = MockRepository.GenerateMock<IView>();
viewResult.ExecuteResult(context);
Assert.AreEqual("text/javascript", context.HttpContext.Response.ContentType);
}
As suggested by (among others) Kazi Manzur Rashid in this blog post, I am using ActionFilterAttributes to transfer model state from one request to another when redirecting.
However, I find myself unable to write a unit test that test the behavior of these attributes. As an example, this what I want the test for the ImportModelStateAttribute to do:
Setup the filterContext so that TempData[myKey] contains some fake "exported" ModelState (that is, a ModelStateDictionary I create myself, and add one error to)
Make ModelState contain one model error.
Call OnActionExecuting.
Verify the two dictionaries are merged, and ModelState now contains both errors.
I'm at a loss already on the first step.
EDIT:
Yes, I've tried mocking ActionFilterAttribute with Moq, but I get errors stating
Invalid setup on non-overridable member
for both TempData and ModelState.
Tomas, You do not have to mock the filterContext, you can create the real object for testing the action filter, the same goes for the model state, these are poco objects. Only thing that you have to mock is the HttpContext (if needed).
[Fact]
public void Should_import_complete_view_data()
{
var attribute = new ImportViewDataFromTempDataAttribute();
var httpContext = new Mock<HttpContextBase>();
var requestContext = new RequestContext(httpContext.Object, new RouteData());
var previousModel = new object();
var previousViewData = new ViewDataDictionary(previousModel) {{"foo", "bar"}};
previousViewData.ModelState.AddModelError("foo", "bar");
var controller = new Mock<ControllerBase>();
controller.Object.ViewData = new ViewDataDictionary();
controller.Object.TempData = new TempDataDictionary { { attribute.Key, previousViewData } };
var controllerContext = new ControllerContext(requestContext, controller.Object);
var actionContext = new ActionExecutingContext(controllerContext, new Mock<ActionDescriptor>().Object, new Dictionary<string, object>());
attribute.OnActionExecuting(actionContext);
Assert.True(actionContext.Controller.ViewData.ContainsKey("foo"));
Assert.True(actionContext.Controller.ViewData.ModelState.ContainsKey("foo"));
Assert.Same(previousModel, actionContext.Controller.ViewData.Model);
}