Я пытаюсь протестировать Index
действие контроллера. Действие использует AutoMapper для отображения домена Customer
возразите против модели представления TestCustomerForm
. В то время как это работает, я обеспокоен лучшим способом протестировать результаты, которые я получаю из Index
действие.
Индексное действие контроллера похоже на это:
public ActionResult Index()
{
TestCustomerForm cust = Mapper.Map(_repository.GetCustomerByLogin(CurrentUserLoginName));
return View(cust);
}
И TestMethod
похож на это:
[TestMethod]
public void IndexShouldReturnCustomerWithMachines()
{
// arrange
var customer = SetupCustomerForRepository(); // gets a boiler plate customer
var testController = CreateTestController();
// act
ViewResult result = testController.Index() as ViewResult;
// assert
Assert.AreEqual(customer.MachineList.Count(),
(result.ViewData.Model as TestCustomerForm).MachineList.Count());
}
В CreateTestController
метод я использую Rhino.Mocks
дразнить клиентский репозиторий и настраивать его для возврата клиента из SetupCustomerForRepository
. Этим способом я знаю, что репозиторий возвратит намеченного клиента когда Index
вызовы действия _repository.GetCustomerByLogin(CurrentUserLoginName)
. Поэтому я изображаю утверждение, что равное количество соответствует для удовлетворения IndexShouldReturnCustomerWithMachines
.
Все это сказало, что я заинтересован относительно того, что я должен тестировать.
result.ViewData.Model as TestCustomerForm
. Это - действительно проблема? Это касается меня, потому что в этом экземпляре я действительно не делаю разработки через тестирование, и кажется, что я рассчитываю на конкретную реализацию для удовлетворения теста.TestCustomerForm
?Это одна из причин, по которой мы перемещаем AutoMapper в настраиваемый ActionResult или ActionFilter. В какой-то момент вы действительно хотите только проверить, сопоставили ли вы Foo с FooDto, но не обязательно проверять фактическое сопоставление. Перемещая AutoMapper в границы слоя (например, между контроллером и представлением), вы можете просто проверить, что вы говорите AutoMapper делать.
Это похоже на тестирование ViewResult. Вы не проверяете с контроллера, что представление было отрисовано, а скорее вы сказали MVC отрендерить такое-то представление. Результатом нашего действия будет:
public class AutoMapViewResult : ActionResult
{
public Type SourceType { get; private set; }
public Type DestinationType { get; private set; }
public ViewResult View { get; private set; }
public AutoMapViewResult(Type sourceType, Type destinationType, ViewResult view)
{
SourceType = sourceType;
DestinationType = destinationType;
View = view;
}
public override void ExecuteResult(ControllerContext context)
{
var model = Mapper.Map(View.ViewData.Model, SourceType, DestinationType);
View.ViewData.Model = model;
View.ExecuteResult(context);
}
}
С помощью вспомогательного метода в базовом классе контроллера:
protected AutoMapViewResult AutoMapView<TDestination>(ViewResult viewResult)
{
return new AutoMapViewResult(viewResult.ViewData.Model.GetType(), typeof(TDestination), viewResult);
}
Что затем заставляет контроллер теперь указывать только то, что отображать в / из, вместо того, чтобы выполнять фактическое отображение:
public ActionResult Index(int minSessions = 0)
{
var list = from conf in _repository.Query()
where conf.SessionCount >= minSessions
select conf;
return AutoMapView<EventListModel[]>(View(list));
}
На этом этапе, Мне нужно только проверить: «Убедитесь, что вы сопоставляете этот объект Foo с этим целевым типом FooDto», без необходимости выполнять сопоставление.
РЕДАКТИРОВАТЬ:
Вот пример тестового фрагмента:
var actionResult = controller.Index();
actionResult.ShouldBeInstanceOf<AutoMapViewResult>();
var autoMapViewResult = (AutoMapViewResult) actionResult;
autoMapViewResult.DestinationType.ShouldEqual(typeof(EventListModel[]));
autoMapViewResult.View.ViewData.Model.ShouldEqual(queryResult);
autoMapViewResult.View.ViewName.ShouldEqual(string.Empty);
Я бы, вероятно, разделил связь между AutoMapper
и контроллером, вводя абстракцию:
public interface IMapper<TSource, TDest>
{
TDest Map(TSource source);
}
public CustomerToTestCustomerFormMapper: IMapper<Customer, TestCustomerForm>
{
static CustomerToTestCustomerFormMapper()
{
// TODO: Configure the mapping rules here
}
public TestCustomerForm Map(Customer source)
{
return Mapper.Map<Customer, TestCustomerForm>(source);
}
}
Затем вы передаете это в контроллер:
public HomeController: Controller
{
private readonly IMapper<Customer, TestCustomerForm> _customerMapper;
public HomeController(IMapper<Customer, TestCustomerForm> customerMapper)
{
_customerMapper = customerMapper;
}
public ActionResult Index()
{
TestCustomerForm cust = _customerMapper.Map(
_repository.GetCustomerByLogin(CurrentUserLoginName)
);
return View(cust);
}
}
И в своем модульном тесте вы должны использовать свой любимый фреймворк для фиксации этого преобразователя.