使用Automapper映射ViewModel之后我应该测试什么?

我试图测试控制器的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

所有这些都说我担心我应该测试什么。

  1. result.ViewData.Model as TestCustomerForm似乎result.ViewData.Model as TestCustomerForm 。 这真的是一个问题吗? 这让我很担心,因为在这种情况下,我并没有真正进行测试驱动开发,而且我似乎依靠特定的实现来满足测试。
  2. 是否有更合适的测试来确保正确的映射?
  3. 我应该从TestCustomerForm测试每个映射属性吗?
  4. 我应该做更多一般的控制器动作测试吗?

这是我们将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(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(View(list)); } 

此时,我只需要测试“确保将此Foo对象映射到此目标FooDto类型”,而无需实际执行映射。

编辑:

以下是测试代码段的示例:

 var actionResult = controller.Index(); actionResult.ShouldBeInstanceOf(); 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 { TDest Map(TSource source); } public CustomerToTestCustomerFormMapper: IMapper { static CustomerToTestCustomerFormMapper() { // TODO: Configure the mapping rules here } public TestCustomerForm Map(Customer source) { return Mapper.Map(source); } } 

接下来,将其传递给控制器​​:

 public HomeController: Controller { private readonly IMapper _customerMapper; public HomeController(IMapper customerMapper) { _customerMapper = customerMapper; } public ActionResult Index() { TestCustomerForm cust = _customerMapper.Map( _repository.GetCustomerByLogin(CurrentUserLoginName) ); return View(cust); } } 

在您的unit testing中,您将使用您最喜欢的模拟框架来存根此映射器。