2010-06-21 13 views
11

Estoy intentando probar la acción Index de un controlador. La acción utiliza AutoMapper para asignar un objeto de dominio Customer a un modelo de vista TestCustomerForm. Mientras esto funciona, me preocupa la mejor manera de probar los resultados que recibo de la acción Index.Después de usar Automapper para mapear un ViewModel ¿cómo y qué debo probar?

acción index del controlador se parece a esto:

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

    return View(cust); 
} 

Y su TestMethod se parece a esto:

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

En el método CreateTestController utilizo Rhino.Mocks para burlarse de un repositorio del cliente y configurarlo para devolver al cliente al SetupCustomerForRepository. De esta manera, sé que el repositorio devolverá al cliente previsto cuando la acción Index llame al _repository.GetCustomerByLogin(CurrentUserLoginName). Por lo tanto, creo que afirmar que un recuento igual es adecuado para satisfacer IndexShouldReturnCustomerWithMachines.

Todo lo dicho, me preocupa lo que debería probar.

  1. Parece presuntuoso lanzar el result.ViewData.Model as TestCustomerForm. ¿Es esto realmente un problema? Esto me preocupa porque en este caso, realmente no estoy haciendo un desarrollo basado en pruebas y parece que estoy contando con una implementación particular para satisfacer la prueba.
  2. ¿Existen pruebas más adecuadas para garantizar una asignación correcta?
  3. ¿Debo estar probando cada propiedad mapeada del TestCustomerForm?
  4. ¿Hay más pruebas generales de acción del controlador que debería hacer?

Respuesta

15

Esta es una de las razones por las que movemos AutoMapper en un ActionResult personalizado o un ActionFilter. En algún momento, solo desea probar que asignó Foo a FooDto, pero no necesariamente prueba la asignación real. Al mover AutoMapper a los límites de la capa (como entre una vista de controlador y otra), puede simplemente probar lo que le está diciendo a AutoMapper que haga.

Esto es similar a probar un ViewResult. No evalúa desde un controlador que se visualizó una vista, sino que le indicó a MVC que visualice tal y tal vista.Nuestro resultado acción se convierte en:

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

Con un método de ayuda en una clase base:

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

Que a su vez hace que el controlador ahora sólo especifican qué mapa de/a, en lugar de realizar el mapeo real :

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

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

en este punto, sólo tengo que probar "asegúrese de que usted está inyectando este objeto Foo a este destino FooDto tipo", sin necesidad de llevar a cabo realmente el Mappi ng.

EDIT:

Aquí hay un ejemplo de un fragmento de prueba:

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

Gran respuesta que tiene mucho sentido. Para la posteridad, ¿te importaría agregar tu declaración de prueba? – ahsteele

+1

¿Cómo funcionaría esto con el nuevo WebApi, donde mi método Get devuelve un IEnumerable y no un resultado de acción? – shashi

+0

@sassyboy Tiendo a usar una capa de servicio aislada con API web, donde puedes crear una abstracción similar tuya. –

2

que probablemente separar el acoplamiento entre AutoMapper y el controlador mediante la introducción de una abstracción:

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

A continuación se pasa esto en el controlador:

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

Y en su unidad de prueba que usaría su marco burlón favorito para cortar este mapeador.

+0

Estas pruebas son en el extremo inferior de valor. Si se burla de AutoMapper, ¿qué es exactamente lo que está probando, ese mapa se llama? No hay lógica de flujo, etc. solo permite obtener una mayor cobertura de prueba. Cuando sus controladores son tan delgados (la complejidad se traslada a carpetas, filtros, invocadores de acciones, etc.) simplemente no los pruebe con "Unidad" (espera la llama) –

+0

@mattcodes, esta acción del controlador hace tres cosas que deben probarse: utiliza un repositorio (¡fingir!), el resultado de este repositorio se mapea a otro tipo (¡simulacro!), el resultado del mapeo se devuelve a la vista. Donde este repositorio recupera los datos y cómo se realiza la asignación es de poco valor para el controlador y debe probarse por separado. Como alternativa, por supuesto, puede decir que esta acción no necesita ser probada, pero la pregunta de OP era exactamente sobre pruebas unitarias, así que decidí dar mis dos centavos :-) –

Cuestiones relacionadas