2010-06-20 186 views
4

我想用Rhino.Mocks来模拟ControllerContext对象,以便在我的控制器单元测试中访问像User,Request,Response和Session这样的运行时对象。我已经写了下面的方法来模拟控制器。如何使用Rhino.Mocks来模拟ControllerContext

private TestController CreateTestControllerAs(string userName) 
{ 
    var mock = MockRepository.GenerateStub<ControllerContext>(); 
    mock.Stub(con => 
     con.HttpContext.User.Identity.Name).Return(userName); 
    mock.Stub(con => 
     con.HttpContext.Request.IsAuthenticated).Return(true); 

    var controller = CreateTestController(); // left out of example for brevity 
    controller.ControllerContext = mock; 

    return controller; 
} 

但是,我嘲笑ControllerContext的HttpContext为空,有我尝试访问HttpContext.User等引起System.NullReferenceException

我在做什么错误与我的嘲笑?

回答

5

我强烈建议您查看MVCContrib.TestHelper,它使用Rhino.Mocks并提供了一种优雅的方式来测试您的控制器。这是你的测试可能会什么样子:

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

这里是一个unit testcontroller这是一个sample ASP.NET MVC application我写的一部分。

+0

这工作得很好。仍然必须将一个对象放在'HttpContext.User'属性中,但这很容易通过执行:'HttpContext.User = new GenericPrincipal(new GenericIdentity(loginName),null);' – ahsteele 2010-06-21 03:16:49

0

我认为,问题是,你需要存根属性的整条产业链,或者至少传递到您的ControllerContext模拟一个的HttpContext,即沿着线的东西:

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

在代码中,给出你从来没有专门设置HttpContext为任何东西,默认情况下你的Stub假定它是空的。

我还没有使用Darin描述的解决方案,但它看起来会让你的生活变得更容易!

1

其他答案已经显示你如何嘲笑一个财产链来解决你的问题。

但是,真正的问题在于,如果你违反了law of demeter,单元测试和嘲讽并不能很好地工作。如果你希望你的代码是可测试的并且是最大可重用的,那么你需要直接注入代码的真实依赖关系,并在抽象背后隐藏这些依赖关系。

例如,而不是这样做:

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

这样做:

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

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

通过引入INameProvider概念,组件代码,测试和模拟变得更简单。您的代码也变得更加可重用:它只依赖于“名称提供者”的抽象概念,而不是一堆ASP.NET类。只要有可能实现INameProvider适配器,您就可以在任何环境中重用您的组件。

权衡是你需要声明INameProvider接口并编写一个实现它的包装类。当你一贯地遵循这种方法时,你会得到很多小的接口和适配器类。这就是测试驱动开发的方式。 (如果你想知道为什么我引入INameProvider而不是直接设置名称 - 这是为了让IoC容器可以使用接口来匹配依赖和实现。)

+0

这是一种常用方法,但会导致用自定义代码复制mvc层。即用户的INameProvider,控制器的服务类,会话... Cookie,请求,响应以及更多的抽象概率是动作结果。 – 2015-09-25 19:26:39

相关问题