2010-06-21 7 views
11

Próbuję przetestować działanie kontrolera Index. Akcja używa AutoMapper do mapowania obiektu domeny Customer do modelu widoku TestCustomerForm. Podczas tej operacji obawiam się najlepszego sposobu przetestowania wyników, które otrzymuję z działania Index.Po użyciu Automappera do mapowania ViewModel'a jak i co powinienem przetestować?

akcja index kontrolera wygląda następująco:

public ActionResult Index() 
{ 
    TestCustomerForm cust = Mapper.Map<Customer, 
     TestCustomerForm>(_repository.GetCustomerByLogin(CurrentUserLoginName)); 

    return View(cust); 
} 

I jego TestMethod wygląda następująco:

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

W metodzie CreateTestController używam Rhino.Mocks mock repozytorium klientów i ustawić go do zwróć klienta z SetupCustomerForRepository. W ten sposób wiem, że repozytorium zwróci zamierzonego klienta po wywołaniu akcji Index_repository.GetCustomerByLogin(CurrentUserLoginName). Dlatego uważam, że zapewnienie równej liczby jest wystarczające, aby zadowolić IndexShouldReturnCustomerWithMachines.

Powiedziałem, że jestem zaniepokojony tym, co powinienem przetestować.

  1. Wydaje się nierozsądne, aby obsadzić result.ViewData.Model as TestCustomerForm. Czy to naprawdę problem? Dotyczy to mnie, ponieważ w tym przypadku nie prowadzę rozwoju opartego na testach i wydaje mi się, że liczę na konkretną implementację, aby spełnić test.
  2. Czy są odpowiednie testy w celu zapewnienia poprawnego mapowania?
  3. Czy powinienem testować każdą zmapowaną właściwość z TestCustomerForm?
  4. Czy powinienem wykonać więcej ogólnych testów działania kontrolera?

Odpowiedz

15

Jest to jeden z powodów, dla których przenosimy aplikację AutoMapper do niestandardowego ActionResult lub ActionFilter. W pewnym momencie naprawdę chcesz tylko przetestować mapowanie Foo na FooDto, ale niekoniecznie przetestuj faktyczne mapowanie. Przesuwając AutoMappera do granic warstwy (np. Pomiędzy kontrolerem a widokiem), możesz po prostu przetestować to, co chcesz, aby zrobił AutoMapper.

Jest to podobne do testowania ViewResult. Nie testujesz z kontrolera, że ​​widok został wyrenderowany, ale raczej nakazałeś MVC, aby renderował taki widok.Nasz wynik działania jest:

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

Z metody pomocnika w klasie kontrolera Base:

protected AutoMapViewResult AutoMapView<TDestination>(ViewResult viewResult) 
{ 
    return new AutoMapViewResult(viewResult.ViewData.Model.GetType(), typeof(TDestination), viewResult); 
} 

które następnie sprawia, że ​​kontroler teraz tylko określić co do map do/z, zamiast przeprowadzać rzeczywiste odwzorowanie :

public ActionResult Index(int minSessions = 0) 
{ 
    var list = from conf in _repository.Query() 
       where conf.SessionCount >= minSessions 
       select conf; 

    return AutoMapView<EventListModel[]>(View(list)); 
} 

w tym momencie, tylko trzeba przetestować „upewnij się, że masz mapowanie tej Foo obiekt do tego celu rodzaju FooDto”, bez konieczności rzeczywiście wykonać MAPPI ng.

EDIT:

Oto przykład fragmentu testu:

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

Świetna odpowiedź, która ma wiele sensu. Czy chcesz dodać swoje oświadczenie testowe dla potomności? – ahsteele

+1

Jak to działa z nową WebApi, gdzie moja metoda Get zwraca IEnumerable , a nie wynik akcji? – shashi

+0

@sassyboy Używam odizolowanej warstwy usług z interfejsem sieci web, gdzie możesz utworzyć podobną własną abstrakcję. –

2

Prawdopodobnie oddzielić sprzężenia AutoMapper i kontrolera poprzez wprowadzenie abstrakcji:

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

Następny przejechania to do kontrolera:

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

A w badanej jednostki ty użyłbyś twojego ulubionego szyderczego framingu, by zagnieździć tego programistę.

+0

Testy te są na niskim końcu wartości. Jeśli kpisz z AutoMappera, co dokładnie testujesz, nazywa się Map? Nie ma logiki przepływu itp., A tylko pozwala uzyskać wyższy zasięg testu. Kiedy twoje kontrolery są tak cienkie (złożoność jest przenoszona do segregatorów, filtrów, wywołań akcji itd.), To po prostu nie "testuj ich" (czeka na płomień) –

+0

@mattcodes, ta akcja kontrolera wykonuje trzy rzeczy, które muszą być przetestowane: korzysta z repozytorium (to jest szydło!), wynik tego repozytorium jest odwzorowany na inny typ (kpiny to!), wynik odwzorowania jest zwracany do widoku. Tam, gdzie to repozytorium pobiera dane i sposób ich mapowania ma niską wartość dla kontrolera i powinno być testowane osobno. Alternatywnie możesz oczywiście powiedzieć, że to działanie nie musi być testowane, ale pytanie OP dotyczyło właśnie testów jednostkowych, więc postanowiłem dać moje dwa centy :-) –

Powiązane problemy