如何使用 Rhino.Mocks 来模拟 ControllerContext

发布于 2024-09-06 08:13:43 字数 757 浏览 3 评论 0 原文

我正在尝试使用 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 为 null,并且我尝试访问 HttpContext.User 等会导致 System.NullReferenceException

我的嘲笑做错了什么?

I am trying to use Rhino.Mocks to mock up a ControllerContext object to gain access to runtime objects like User, Request, Response, and Session in my controller unit tests. I've written the below method in an attempt to mock up a controller.

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

However, the HttpContext of my mocked ControllerContext is null and there my attempts to access HttpContext.User etc. cause a System.NullReferenceException.

What am I doing wrong with my mocking?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

旧情勿念 2024-09-13 08:13:43

我强烈建议您查看使用 Rhino.Mocks< 的 MVCContrib.TestHelper /code> 并提供了一种测试控制器的优雅方法。您的测试可能如下所示:

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

这是一个 控制器的">单元测试 这是我编写的示例 ASP.NET MVC 应用程序的一部分。

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.

芸娘子的小脾气 2024-09-13 08:13:43

其他答案已经展示了如何模拟财产链来解决您的问题。

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

例如,不要这样做:

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 容器可以使用该接口来将依赖项与实现相匹配。)

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.)

朱染 2024-09-13 08:13:43

我相信问题在于您需要存根整个属性链,或者至少将 HttpContext 传递给您的 ControllerContext Mock,即类似以下内容的内容:

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 设置为任何具体内容,通过默认情况下,您的存根假定它为空。

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

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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文