Как я могу использовать Rhino.Mocks для насмешки ControllerContext - PullRequest
4 голосов
/ 21 июня 2010

Я пытаюсь использовать 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;
 }

Однако, HttpContext моего поддельного ControllerContext равно нулю, и там мои попытки доступа к HttpContext.User и т. Д. Вызывают System.NullReferenceException.

Что я делаю не так с моим издевательством?

Ответы [ 3 ]

5 голосов
/ 21 июня 2010

Я настоятельно рекомендую вам взглянуть на 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();
    }
}

А вот модульный тест для контроллера , являющегося частью примера приложения ASP.NET MVC , который я написал.

1 голос
/ 22 июня 2010

Другие ответы уже показали, как вы можете смоделировать цепочку свойств, чтобы обойти вашу проблему.

Но реальная проблема здесь в том, что модульное тестирование и имитация не очень хорошо работают, если вы нарушаете закон Деметры .Если вы хотите, чтобы ваш код был тестируемым и максимально пригодным для повторного использования, то вам нужно напрямую внедрить реальные зависимости вашего кода и скрыть эти зависимости за абстракциями.

Например, вместо того, чтобы делать это:

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 голосов
/ 21 июня 2010

Я считаю, что проблема заключается в том, что вам нужно заглушить всю цепочку свойств или, по крайней мере, передать вашему 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 на что-либо определенное, по умолчанию ваша заглушка предполагает, что он нулевой.

Я не использовал решение, которое описывает Дарин, но похоже, что это сделает вашу жизнь намного проще!

...