2009-09-17 10 views
5

¿Cómo puedo simular una DataServiceQuery para la prueba de unidad?Burlarse de una DataServiceQuery <TElement>

detalles largas siguen: Imagínese una aplicación ASP.NET MVC, donde las conversaciones controlador a una DataService ADO.NET que encapsula el almacenamiento de nuestros modelos (por ejemplo, el bien vamos a estar leyendo una lista de clientes). Con una referencia al servicio, tenemos una clase generada heredando de DataServiceContext:

namespace Sample.Services 
{ 
    public partial class MyDataContext : global::System.Data.Services.Client.DataServiceContext 
    { 
    public MyDataContext(global::System.Uri serviceRoot) : base(serviceRoot) { /* ... */ } 

    public global::System.Data.Services.Client.DataServiceQuery<Customer> Customers 
    { 
     get 
     { 
     if((this._Customers==null)) 
     { 
      this._Customers = base.CreateQuery<Customer>("Customers"); 
     } 
     return this._Customers; 
     } 
    } 
    /* and many more members */ 
    } 
} 

el controlador podría ser:

namespace Sample.Controllers 
{ 
    public class CustomerController : Controller 
    { 
    private IMyDataContext context; 

    public CustomerController(IMyDataContext context) 
    { 
     this.context=context; 
    } 

    public ActionResult Index() { return View(context.Customers); } 
    } 
} 

Como se puede ver, he utilizado un constructor que acepta una instancia IMyDataContext por lo que podemos utilizar una maqueta en nuestra unidad de prueba:

[TestFixture] 
public class TestCustomerController 
{ 
    [Test] 
    public void Test_Index() 
    { 
    MockContext mockContext = new MockContext(); 
    CustomerController controller = new CustomerController(mockContext); 

    var customersToReturn = new List<Customer> 
    { 
     new Customer{ Id=1, Name="Fred" }, 
     new Customer{ Id=2, Name="Wilma" } 
    }; 
    mockContext.CustomersToReturn = customersToReturn; 

    var result = controller.Index() as ViewResult; 

    var models = result.ViewData.Model; 

    //Now we have to compare the Customers in models with those in customersToReturn, 
    //Maybe by loopping over them? 
    foreach(Customer c in models) //*** LINE A *** 
    { 
     //TODO: compare with the Customer in the same position from customersToreturn 
    } 
    } 
} 

MockContext y MyDataContext necesitan implementar la misma interfaz de IMyDataContext:

namespace Sample.Services 
{ 
    public interface IMyDataContext 
    { 
    DataServiceQuery<Customer> Customers { get; } 
    /* and more */ 
    } 
} 

Sin embargo, cuando tratamos de implementar la clase MockContext, nos encontramos con problemas debido a la naturaleza de DataServiceQuery (que, para ser claros, estamos utilizando en la interfaz IMyDataContext simplemente porque ese es el tipo de datos que encontramos en la clase MyDataContext autogenerada con la que comenzamos). Si tratamos de escribir:

public class MockContext : IMyDataContext 
{ 
    public IList<Customer> CustomersToReturn { set; private get; } 

    public DataServiceQuery<Customer> Customers { get { /* ??? */ } } 
} 

En los Clientes Getter nos gustaría crear una instancia de una instancia DataServiceQuery, rellenarlo con los clientes en CustomersToReturn, y devolverlo. Los problemas que encuentro:

1 ~ DataServiceQuery no tiene constructor público; Para crear una instancia, debe llamar a CreateQuery en un DataServiceContext; ver MSDN

2 ~ Si hago la MockContext hereda de DataServiceContext así, y llame CreateQuery para obtener una DataServiceQuery de usar, el servicio y la consulta tiene que estar atado a un URI válido y, cuando trato de recorrer o acceder a los objetos en la consulta, intentará y ejecutará contra ese URI. En otras palabras, si cambio el MockContext como tal:

namespace Sample.Tests.Controllers.Mocks 
{ 
    public class MockContext : DataServiceContext, IMyDataContext 
    { 
    public MockContext() :base(new Uri("http://www.contoso.com")) { } 

    public IList<Customer> CustomersToReturn { set; private get; } 

    public DataServiceQuery<Customer> Customers 
    { 
     get 
     { 
     var query = CreateQuery<Customer>("Customers"); 
     query.Concat(CustomersToReturn.AsEnumerable<Customer>()); 
     return query; 
     } 
    } 
    } 
} 

Luego, en la prueba de unidad, obtenemos un error en la línea marcada como LINE A, porque http://www.contoso.com no aloja nuestro servicio. El mismo error se dispara incluso si LINEA A intenta obtener la cantidad de elementos en los modelos. Gracias de antemano.

Respuesta

0

[Renuncia - Trabajo en Typemock]

¿Ha considerado el uso de un marco de burla?

Puede utilizar Typemock aislador para crear una instancia de falsa DataServiceQuery:

var fake = Isolate.Fake.Instance<DataServiceQuery>(); 

Además, puede crear una falsa DataServiceContext similar y ajustar su comportamiento en lugar de tratar de heredarlo.

+0

Dror, gracias por la idea, pero por el momento no estamos utilizando cualquier marco de burla. Nos interesaría ver si hay una solución que no dependa de una. Aún así, gracias – FOR

+0

¿Tiene una razón específica para no utilizar un marco de burla? –

+0

En general, no hay razón específica. Podríamos presentarlo, pero es poco probable que lo hagamos de la noche a la mañana para esta tarea específica. Entonces, digamos que por ahora nos gustaría encontrar una solución sin agregar un marco de burla. – FOR

4

He resuelto mediante la creación de una interfaz IDataServiceQuery con dos implementaciones:

  • DataServiceQueryWrapper
  • MockDataServiceQuery

entonces utilizar IDataServiceQuery dondequiera que antes habrían utilizado un DataServiceQuery.

public interface IDataServiceQuery<TElement> : IQueryable<TElement>, IEnumerable<TElement>, IQueryable, IEnumerable 
{ 
    IDataServiceQuery<TElement> Expand(string path); 

    IDataServiceQuery<TElement> IncludeTotalCount(); 

    IDataServiceQuery<TElement> AddQueryOption(string name, object value); 
} 

El DataServiceQueryWrapper toma un DataServiceQuery en él es constructor y entonces toda la funcionalidad de los delegados a la consulta se ha pasado. Del mismo modo, el MockDataServiceQuery toma un todo IQueryable y los delegados lo posible para la consulta.

Para los métodos simulados IDataServiceQuery, actualmente solo devuelvo this, aunque podría hacer algo para burlarse de la funcionalidad si así lo desea.

Por ejemplo:

// (in DataServiceQueryWrapper.cs) 
public IDataServiceQuery<TElement> Expand(string path) 
{ 
    return new DataServiceQueryWrapper<TElement>(_query.Expand(path)); 
} 

 

// (in MockDataServiceQuery.cs) 
public IDataServiceQuery<TElement> Expand(string path) 
{ 
    return this; 
}