简体   繁体   中英

When I mock my ASP.NET MVC controller, my ActionMethod returns no view. Why?

In my simple Index() ActionMethod I reference the User.Identity property. So, I thought that I need to mock that.

So i create a some mock HomeController and use that in my unit test. When I do that, the ActionMethod returns null as the view. When I replace the mock'd controller with a concrete instance (and of course comment out any reference to User.Identity ) then the correct view is returned.

eg.

// Arrange.
var homeController = Mock<HomeController>(..);
homeController.Setup(x => x.User).Returns(new GenericPrincipal(..));

// Act.
var result = homeController.Index();

// Assert.
Assert.IsNotNull(result); // This fails here. result is NULL.

but when I do this (and comment out any User reference), it works...

// Arrange.
var homeController = new HomeController(..);

// Act.
var result = homeController.Index();

// Assert.
Assert.IsNotNull(result); // Tick!

Any ideas why this is?

There are some strange things in your unit test. You are unit testing a controller and yet you are mocking the creation of the object under test: var homeController = Mock<HomeController>(..); which is incorrect.

Here's the correct way to inject a mocked user into a controller that you are willing to unit test:

// arrange
var sut = new HomeController();
var user = new GenericPrincipal(new GenericIdentity("foo"), null);
var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(x => x.User).Returns(user);
var context = new ControllerContext(new RequestContext(httpContext.Object, new RouteData()), sut);
sut.ControllerContext = context;

// act
var actual = sut.Index();

// assert
...

I think you should be mocking a HttpContext for the controller to work with. I provided one on another answer that you could use . As Steve Rowbotham says, you should be mocking the dependencies of the system under test (ie the dependencies of the controller) and not mocking the system under test itself; you want to test the real version of the controller not a mock :)

Using the HttpContextBase class from the link, you would simply do the following in your test

var controllerToTest = new HomeController();
var context = new MockHttpContextBase(controllerToTest);

// do stuff that you want to test e.g. something goes into session

Assert.IsTrue(context.HttpContext.Session.Count > 0); 

You may go a step further and create Setup and TearDown methods to set the controller up with a mocked context.

To be honest, this looks like a very strange test because you're mocking the system under test (SUT), in other words the HomeController . Usually one would mock the dependencies of the SUT, set the expectations on the mock and inject the mock into the SUT to confirm that it is properly interacting with its dependencies.

When you create a mock of the HomeController , Moq is creating a class that inherits from HomeController and overrides the virtual method Index . So when you call Index on the mock you're not calling the implementation of Index you've defined in the HomeController class but the overridden one. Since you've not explicitly Setup the method on the mock it will return the default value, in this case null .

In your second test you're calling the actual implementation of Index because you're constructing an actual instance of the HomeController class. If you call GetType() on the instance of the mock object then you'll see that it's an instance of a proxy that derives from HomeController which intercepts calls to the publicly defined, overridable methods on the base class (which is a large part of the purpose of a mock object).

I think that your Index method is probably virtual, which causes Moq to replace that function with a mocked function. In order to prevent that, you need to set the CallBase property on the mock.

However, I agree with the other replies that you should not be mocking your controller, but you should mock your dependency.

(an even simpler method is to create a specialized model binder that can fetch the principal from the HttpContext, then you can just pass the principal as input parameter to your method)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM