2010-06-21 3 views
11

Ich versuche, die Index Aktion eines Controllers zu testen. Die Aktion verwendet AutoMapper, um ein Objekt der Domäne Customer einem Ansichtsmodell TestCustomerForm zuzuordnen. Während dies funktioniert, bin ich besorgt über den besten Weg, um die Ergebnisse zu testen, die ich von der Index Aktion erhalten.Nach der Verwendung von Automapper, um ein ViewModel zuzuordnen, wie und was soll ich testen?

Die Index Aktion sieht der Steuerung wie folgt aus:

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

    return View(cust); 
} 

Und seine TestMethod sieht wie folgt aus:

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

Im CreateTestController Methode, die ich Rhino.Mocks verwenden, um einen Kunden-Repository zu verspotten und es bis zu den Kunden von SetupCustomerForRepository zurückgeben. Auf diese Weise weiß ich, dass das Repository den beabsichtigten Kunden zurückgibt, wenn die Index Aktion _repository.GetCustomerByLogin(CurrentUserLoginName) aufruft. Daher halte ich es für ausreichend,zu bestätigen.

All dies sagte, ich bin besorgt, was ich testen sollte.

  1. Es scheint vermessen zu sein, die result.ViewData.Model as TestCustomerForm zu werfen. Ist das wirklich ein Problem? Dies betrifft mich, weil ich in diesem Fall keine wirklich testgetriebene Entwicklung betreibe und es scheint, als würde ich auf eine bestimmte Implementierung zählen, um den Test zu bestehen.
  2. Gibt es geeignetere Tests, um eine korrekte Zuordnung zu gewährleisten?
  3. Sollte ich jede gemappte Eigenschaft von der TestCustomerForm testen?
  4. Gibt es allgemeinere Controller-Action-Tests, die ich tun sollte?

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

    Weiter Sie diese passieren in die Steuerung:

Antwort

15

Dies ist einer der Gründe, warum wir AutoMapper in ein benutzerdefiniertes ActionResult oder ActionFilter verschieben. An einem bestimmten Punkt möchten Sie nur wirklich testen, dass Sie Foo auf FooDto gemappt haben, aber nicht unbedingt das tatsächliche Mapping testen. Wenn Sie den AutoMapper in die Ebenengrenzen verschieben (z. B. zwischen Controller und Ansicht), können Sie lediglich testen, was Sie AutoMapper tun möchten.

Dies ähnelt dem Testen eines ViewResult. Sie testen nicht von einem Controller, dass eine Ansicht gerendert wurde, sondern dass Sie MVC angewiesen haben, diese und jene Ansicht zu rendern.Unser Aktionser wird:

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

mit einer Helfer-Methode auf einer Basis Controller-Klasse:

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

die dann den Controller erst jetzt macht angeben, was zur Karte/aus, anstatt die tatsächliche Zuordnung der Durchführung :

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

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

an dieser Stelle muss ich nur testen „stellen Sie sicher, dass Sie diese Foo-Objekt zu diesem Ziel FooDto Typ kartieren“, ohne die Mappi zu benötigen, um tatsächlich durchführen ng.

EDIT:

Hier ist ein Beispiel eines Test Schnipsel:

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

Große Antwort, die viel Sinn macht. Für die Nachwelt würde es Ihnen etwas ausmachen, Ihre Testaussage hinzuzufügen? – ahsteele

+1

Wie würde das mit dem neuen WebApi funktionieren, bei dem meine Get-Methode ein IEnumerable zurückgibt und kein Aktionsergebnis? – shashi

+0

@sassyboy Ich neige dazu, eine isolierte Service-Schicht mit Web-API zu verwenden, wo Sie eine ähnliche Abstraktion erstellen könnten. –

2

Ich würde wahrscheinlich durch die Einführung einer Abstraktion, die Kopplung zwischen AutoMapper und Controller trennen

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

Und in Ihrem Gerät zu testen Sie würde dein Lieblings-Spott-Framework verwenden, um diesen Mapper zu stubben.

+0

Diese Tests sind am unteren Ende des Wertes. Wenn Sie AutoMapper mokieren, was genau testen Sie, wird diese Karte aufgerufen? Es gibt keine Flusslogik etc .. Es kann nur eine höhere Testabdeckung erhalten. Wenn Ihre Controller so dünn sind (Komplexität wird zu Bindemitteln, Filtern, Aktionsinvokatoren usw. verschoben), dann testen Sie nicht einfach "Unit" (wartet auf Flamme) –

+0

@mattcodes, diese Controller-Aktion macht drei Dinge, die getestet werden müssen: es verwendet ein Repository (mock it!), das Ergebnis dieses Repositorys wird einem anderen Typ zugeordnet (mock it!), das Ergebnis des Mappings wird an die View zurückgegeben. Wo dieses Repository die Daten abruft und wie das Mapping durchgeführt wird, ist für den Controller von geringem Wert und sollte separat getestet werden. Als Alternative könntest du natürlich sagen, dass diese Aktion nicht getestet werden muss, aber die OPs Frage war genau über Unit Testing, also entschied ich mich, meine zwei Cents zu geben :-) –

Verwandte Themen