我正在尝试测试控制器的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
?答案 0 :(得分:15)
这是我们将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);
答案 1 :(得分: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);
}
}
在您的单元测试中,您将使用您最喜欢的模拟框架来存储此映射器。