После использования Automapper для сопоставления ViewModel, как и что я должен проверить?
Я пытаюсь проверить действие Index
контроллера. Действие использует AutoMapper для сопоставления объекта домена Customer
с моделью просмотра TestCustomerForm
. Хотя это работает, меня беспокоит лучший способ проверить результаты, которые я получаю от действия Index
.
Действие индекса контроллера выглядит следующим образом:
public ActionResult Index()
{
TestCustomerForm cust = Mapper.Map<Customer,
TestCustomerForm>(_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
?
- Есть ли более общие тесты действий контроллера, которые я должен делать?
Ответы
Ответ 1
Это одна из причин, по которой мы перемещаем 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 type", без фактического выполнения сопоставления.
EDIT:
Вот пример тестового фрагмента:
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);
Ответ 2
Я бы выделил связь между 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);
}
}
И в вашем unit test вы будете использовать свою любимую фальшивую фреймворк, чтобы заглушить этот картограф.