How do I use Rhino.Mocks to mock a ControllerContext

半腔热情 提交于 2019-11-30 21:59:21

I would strongly recommend you looking at MVCContrib.TestHelper which uses Rhino.Mocks and provides an elegant way to test your controllers. Here's how your test might look like:

[TestClass]
public class UsersControllerTests : TestControllerBuilder
{
    [TestMethod]
    public void UsersController_Index()
    {
        // arrange
        // TODO : this initialization part should be externalized
        // so that it can be reused by other tests
        var sut = new HomeController();
        this.InitializeController(sut);
        // At this point sut.Request, sut.Response, sut.Session, ... are
        // stubed objects on which you could define expectations.

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

        // assert
        actual.AssertViewRendered();
    }
}

And here's an unit test for a controller that is part of a sample ASP.NET MVC application I wrote.

The other answers have already shown how you can mock a property chain to work around your problem.

But the real problem here is that unit testing and mocking don't really work well if you violate the law of demeter. If you want your code to be testable and maximally reusable, then you need to directly inject the real dependencies of your code and hide those dependencies behind abstractions.

For example, instead of doing this:

public class MyClass
{
   public ControllerContext Context { get; set; }

   public void DoSomething()
   {
       // BAD: we're only interested in the name, but instead we've injected 
       // a ControllerContext that can give us a HttpContext that can give us
       // a User that can give us an Identity that can give us the Name.
       string name = Context.HttpContext.User.Identity.Name;
       // etcetera
   }
}

Do this:

public class MyClass
{
    public INameProvider NameProvider { get; set; }

    public void DoSomething()
    {
        // GOOD: we've injected a name provider
        string name = NameProvider.Name;
        // etcetera
    }
}

By introducing the INameProvider concept, your component code, tests and mocks become much simpler. Your code also becomes more reusable: it only has a dependency on the abstract concept of a "name provider", rather than on a bunch of ASP.NET classes. You will be able to reuse your component in any environment as long as it is possible to implement a INameProvider adapter.

The trade-off is that you will need to declare the INameProvider interface and write a wrapper class which implements it. When you consistently follow this approach, you will end up with a lot of small interfaces and adapter classes. Such is the way of test driven development.

(In case you're wondering why I introduce INameProvider instead of setting the name directly - this is so that the IoC container can use the interface to match up the dependency with the implementation.)

I believe the problem is that you need to stub the whole chain of properties, or at least pass to your ControllerContext Mock a HttpContext, i.e. something along the lines of:

private TestController CreateTestControllerAs(string userName)
{
    var mock = MockRepository.GenerateStub<ControllerContext>();
    var context = MockRepository.GenerateStub<IHttpContext>();    
    mock.Stub(con =>
        con.HttpContext).Return(context );
    // etc... with User, Identity ...

    return controller;
 }

In your code, given that you never set the HttpContext to anything specifically, by default your Stub assumes it is null.

I haven't used the solution Darin describes, but it looks like it would make your life much easier!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!