2010-06-21 84 views
11

我正在嘗試測試控制器的Index操作。該操作使用AutoMapper將域Customer對象映射到視圖模型TestCustomerForm。雖然這是有效的,但我擔心測試我從Index操作收到的結果的最佳方法。使用Automapper映射ViewModel後,我應該如何測試?

控制器的索引操作是這樣的:

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. 是否有更多的一般控制器動作測試,我應該做的?

回答

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型」,而無需實際執行mappi NG。

編輯:

這裏的一個試驗片段的例子:

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); 
+0

很好的回答,這很有意義。爲了後代你會介意添加你的測試陳述嗎? – ahsteele 2010-06-21 15:40:33

+1

這將如何與新的WebApi,我的Get方法返回一個IEnumerable 而不是一個行動結果? – shashi 2012-08-31 10:25:24

+0

@sassyboy我傾向於使用帶有web api的獨立服務層,您可以在其中創建類似的抽象。 – 2013-07-12 19:18:57

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

而在你的單元測試你將使用你最喜歡的模擬框架來存根這個映射器。

+0

這些測試是在值的低端。如果你嘲笑AutoMapper究竟在測試什麼,該Map是否被調用?沒有流程邏輯等。它只是讓更高的測試覆蓋率。當你的控制器很薄(複雜性被移動到活頁夾,過濾器,動作調用器等)時,只是不要「單元」測試它們(等待火焰) – 2010-06-21 06:35:06

+0

@mattcodes,這個控制器動作需要測試三件事情:使用一個倉庫(模擬它!),這個倉庫的結果被映射到另一個類型(模擬它!),映射的結果返回到視圖。此存儲庫獲取數據以及映射如何執行對控制器而言價值較低,應單獨測試。作爲另一種選擇,當然你可以說這個動作不需要測試,但是OP的問題與單元測試完全相同,所以我決定給我兩分錢:-) – 2010-06-21 06:39:09