首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用Rhino.Mocks模拟ControllerContext

如何使用Rhino.Mocks模拟ControllerContext
EN

Stack Overflow用户
提问于 2010-06-21 04:21:37
回答 3查看 4.4K关注 0票数 4

我正在尝试使用Rhino.Mocks模拟ControllerContext对象,以便在控制器单元测试中访问运行时对象,如User、Request、Response和Session。我已经编写了下面的方法,试图模拟一个控制器。

代码语言:javascript
复制
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

我的嘲笑做错了什么?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-06-21 04:24:22

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

代码语言:javascript
复制
[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();
    }
}

这是一个controllerunit test,它是我写的sample ASP.NET MVC application的一部分。

票数 5
EN

Stack Overflow用户

发布于 2010-06-22 05:18:46

其他答案已经说明了如何模拟属性链来解决您的问题。

但这里真正的问题是,如果违反了law of demeter,单元测试和模拟就不能很好地工作。如果你希望你的代码是可测试的和最大限度的可重用性,那么你需要直接注入代码的真实依赖项,并将这些依赖项隐藏在抽象后面。

例如,不这样做:

代码语言:javascript
复制
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
   }
}

执行以下操作:

代码语言:javascript
复制
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容器可以使用接口来匹配依赖项与实现。)

票数 1
EN

Stack Overflow用户

发布于 2010-06-21 04:42:20

我认为问题在于你需要截断整个属性链,或者至少传递给你的ControllerContext模拟一个HttpContext,也就是类似如下的东西:

代码语言:javascript
复制
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设置为任何特定的值,那么在默认情况下,您的存根假定它为空。

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3080814

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档