2009-09-17 6 views
5

Comment puis-je simuler un DataServiceQuery à des fins de test unitaire?Mocking a DataServiceQuery <TElement>

Détails long suivent: Imaginer une application ASP.NET MVC, où les pourparlers de contrôleur à un DataService ADO.NET qui encapsule le stockage de nos modèles (par exemple nous allons bien être en train de lire une liste des clients). Avec une référence au service, nous obtenons une classe générée héritant 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 */ 
    } 
} 

Le contrôleur pourrait être:

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

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

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

Comme vous pouvez le voir, j'ai utilisé un constructeur qui accepte une instance IMyDataContext si que nous pouvons utiliser une maquette dans notre test unitaire:

[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 et MyDataContext doivent implémenter la même interface IMyDataContext:

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

Cependant, lorsque nous essayons de mettre en œuvre la classe MockContext, nous rencontrons des problèmes en raison de la nature de DataServiceQuery (qui, pour être clair, nous utilisons dans l'interface IMyDataContext simplement parce que ce type de données que nous avons trouvé dans la classe MyDataContext générée automatiquement avec laquelle nous avons démarré). Si nous essayons d'écrire:

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

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

Dans les clients getter nous aimerions instancier une instance DataServiceQuery, remplir avec les clients dans CustomersToReturn, et le retourner. Les problèmes que je rencontre:

1 ~ DataServiceQuery n'a aucun constructeur public; pour instancier celui que vous devez appeler CreateQuery sur un DataServiceContext; voir MSDN

2 ~ Si je fais le MockContext héritera de DataServiceContext ainsi, et appeler CreateQuery pour obtenir un DataServiceQuery à utiliser, le service et la requête doivent être liés à un URI valide et, lorsque je tente de itérer ou accéder aux objets dans la requête, il va essayer de s'exécuter par rapport à cet URI. Autrement dit, si je change le MockContext en tant que tel:

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

Ensuite, dans le test unitaire, nous obtenons une erreur sur la ligne marquée comme la ligne A, parce que http://www.contoso.com ne héberge notre service. La même erreur est déclenchée même si LINE A essaie d'obtenir le nombre d'éléments dans les modèles. Merci d'avance.

Répondre

0

[Avertissement - Je travaille à Typemock]

Avez-vous envisagé d'utiliser un cadre moqueur?

Vous pouvez utiliser Typemock Isolateur pour créer une instance de faux DataServiceQuery:

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

Et vous pouvez créer un faux DataServiceContext similaire et régler son comportement au lieu d'essayer d'en hériter.

+0

Dror, merci pour l'idée, mais pour le moment nous ne sommes pas utiliser un cadre moqueur. Nous serions intéressés de voir s'il y a une solution qui ne repose pas sur un. Encore, merci – FOR

+0

Avez-vous une raison spécifique de ne pas utiliser un cadre moqueur? –

+0

En général, pas de raison particulière. Nous pourrions l'introduire, mais il est peu probable que nous le fassions du jour au lendemain pour cette tâche spécifique. Donc, disons que pour l'instant nous aimerions trouver une solution sans ajouter un cadre moqueur. – FOR

4

Je résolu ce problème en créant une interface IDataServiceQuery avec deux implémentations:

  • DataServiceQueryWrapper
  • MockDataServiceQuery

je puis utiliser IDataServiceQuery partout où j'aurais déjà utilisé 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); 
} 

Le DataServiceQueryWrapper prend un DataServiceQuery dans son constructeur et délègue ensuite toutes les fonctionnalités à la requête transmise. De même, le MockDataServiceQuery prend un tout IQueryable et les délégués qu'il peut à la requête.

Pour les méthodes IDataServiceQuery faux, je viens actuellement de retourner this, même si vous pourriez faire quelque chose pour simuler la fonctionnalité si vous voulez.

Par exemple:

// (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; 
}