Как мои ASP.NET MVC контроллеры должны знать о хранилище - PullRequest
5 голосов
/ 27 апреля 2011

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

Во время модульных тестов я, очевидно, хотел бы создать фиктивный репозиторий, но как мне передать этот фиктивный репозиторий тестируемому экземпляру Controller?Кроме того, как реальный репозиторий, который действительно связан с базой данных, найдет свой путь к контроллеру?

Я просто делаю это через конструкторы, как я показал ниже?Я думаю, что именно так я должен настроить свои контроллеры, но я хотел бы получить подтверждение того, что это правильно:

public class SampleController : Controller
{
    private IRepository _repo;

    //Default constructor uses a real repository
    // new ConcreteRepo() could also be replaced by some static 
    // GetRepository() method somewhere so it would be easy to modify
    //which concrete IRepository is being used
    public SampleController():this(new ConcreteRepo())
    {

    }

    //Unit tests pass in mock repository here
    public SampleController(IRepository repo)
    {
        _repo = repo;
    }
}

Ответы [ 4 ]

3 голосов
/ 27 апреля 2011

Как уже сказали все, вы захотите использовать контейнер IoC * или DI **. Но они не сказали, почему это так.

Идея состоит в том, что контейнер DI позволит вам обойти стратегию построения контроллера ASP.NET MVC по умолчанию, требующую конструктор без параметров. Таким образом, вы можете сделать так, чтобы ваши контроллеры явно указывали свои зависимости (как предпочтительно интерфейсы). То, как эти интерфейсы отображаются на конкретные экземпляры, является делом контейнера DI, и вы должны настроить его либо в Global.asax.cs (в режиме реального времени), либо в настройках своего тестового оборудования (для модульного тестирования).

Это означает, что вашему контроллеру не нужно ничего знать о конкретных реализациях его зависимостей, и поэтому мы следуем Принципу инверсии зависимостей : «Модули высокого уровня не должны зависеть от модулей низкого уровня. И то, и другое должно зависеть от абстракций. "

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

// In Global.asax.cs's Application_Start
using Autofac;
using Autofac.Integration.Mvc;

var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.Register<IRepository>(() => new ConcreteRepo());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

// In your unit test:
var controllerInstance = new SampleController(new InMemoryFakeRepo());

// In SampleController
public class SampleController : Controller
{
    private readonly IRepository _repo;

    public SampleController(IRepository repo)
    {
        _repo = repo;
    }

    // No parameterless constructor! This is good; no accidents waiting to happen!
    // No dependency on any particular concrete repo! Excellent!
}

* IoC = инверсия управления
** DI = инверсия зависимостей
(эти два термина часто взаимозаменяемы, что не совсем верно для ИМО)

3 голосов
/ 27 апреля 2011

Да, вы правы, вы передаете это своему конструктору, как будто оно у вас есть.Путем насмешки IRepository вы явно гарантируете, что зависимый от базы данных код не не попадет в контроллер для тестирования, как вы хотите.

Когда вы действительно запустите его, вы захотитенастройте приложение для работы с инвертированным контейнером управления, чтобы эти зависимости могли быть введены в ваш контроллер (некоторые популярные: Ninject , StructureMap и Windsor ).

Вот пример тестирования с использованием Moq :

private Mock<IRepository> _mockRepo;
private SampleController _controller;

[TestInit]
public void InitTest()
{
    _mockRepo = new Mock<IRepository>();
    _controller = new SampleController(_mockRepo.Object);
}

[Test]
public void Some_test()
{
    _mockRepo.Setup(mr => mr.SomeRepoCall()).Returns(new ValidObject());

    var result = _controller.SomeAction() as ViewResult;
    Assert.IsNotNull(result);
}

Теперь вы можете проверить свои действия и высмеивать IRepository, чтобы вести себя так, как вы хотите.

0 голосов
/ 27 апреля 2011

Для реального взгляните на ninject mvc 3 на nuget, для модульного тестирования я предпочитаю использовать поддельные объекты с коллекциями известных данных в памяти

0 голосов
/ 27 апреля 2011

Лучший ответ, который я знаю, это использовать Ioc-контейнер: http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

Я предпочитаю Замок Виндзор

С передачей зависимостей контроллера вы можете создавать макеты. У нас есть зависимости, которые реализуют интерфейсы, которые можно смоделировать.

...