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

时间:2010-06-21 04:50:10

标签: c# asp.net-mvc tdd automapper mvccontrib-testhelper

我正在尝试测试控制器的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

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

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

2 个答案:

答案 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);
    }
}

在您的单元测试中,您将使用您最喜欢的模拟框架来存储此映射器。