2009-09-17 7 views
5

Wie kann ich eine DataServiceQuery für Unit-Test-Zwecke verspotten?Verspotten eines DataServiceQuery <TElement>

Lange Einzelheiten folgen: eine ASP.NET MVC-Anwendung Stellen Sie sich vor, in dem die Steuerung spricht mit einem ADO.NET Dataservice, das die Speicherung unserer Modelle kapselt (zum Beispiel aus Gründen werden wir eine Liste der Kunden liest). Mit einem Verweis auf den Dienst, erhalten wir eine generierte Klasse von Dataservicecontext erben:

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 */ 
    } 
} 

Der Regler könnte:

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

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

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

Wie Sie sehen können, habe ich einen Konstruktor, der so eine IMyDataContext Instanz akzeptiert dass wir ein Modell in unserer Unit-Test verwenden:

[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 und MyDataContext müssen die gleiche Schnittstelle IMyDataContext implementieren:

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

Wenn wir jedoch die MockContext Klasse versuchen und implementieren, führen wir in Probleme aufgrund der Art von Dataservicequery (was klar zu sein, wir sind einfach in der IMyDataContext Schnittstelle verwenden, da, dass der Typ Daten ist fanden wir in der automatisch generierten MyDataContext-Klasse, mit der wir begonnen haben). Wenn wir zu schreiben versuchen:

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

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

In den Kunden Getter möchten wir eine Instanz Dataservicequery instanziiert, füllen Sie es mit den Kunden in CustomersToReturn, und es zurückgeben. Die Probleme, denen ich begegne:

1 ~ DataServiceQuery hat keinen öffentlichen Konstruktor; Um einen zu instanziieren, sollten Sie CreateQuery für einen DataServiceContext aufrufen. siehe MSDN

2 ~ Wenn ich die MockContext von Dataservicecontext erben machen als auch, und rufen Sie Create eine Dataservicequery zu bekommen zu verwenden, den Service und die Abfrage haben einen gültigen URI und gebunden werden, wenn ich versuche, iterieren oder auf die Objekte in der Abfrage zugreifen, wird es gegen diesen URI versuchen und ausführen. Mit anderen Worten, wenn ich die MockContext als solche zu ändern:

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

Dann wird in dem Unit-Test, erhalten wir einen Fehler auf der Linie als LINE A markiert, weil http://www.contoso.com nicht unseren Service hostet. Derselbe Fehler wird auch dann ausgelöst, wenn LINE A versucht, die Anzahl der Elemente in Modellen abzurufen. Vielen Dank im Voraus.

Antwort

0

[Disclaimer - ich Typemock arbeiten]

Haben Sie darüber nachgedacht einen Mockframework mit?

können Sie Typemock Trenner verwenden, um eine gefälschte Instanz von Dataservicequery zu erstellen:

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

Und Sie können eine ähnliche gefälschte Dataservicecontext erstellen und es Verhalten ist, anstatt zu versuchen, es zu erben.

+0

Dror, danke für die Idee, aber für die Zeit, indem wir sind keine Mockframework. Wir wären daran interessiert zu sehen, ob es eine Lösung gibt, die nicht auf einem beruht. Immer noch, danke – FOR

+0

Haben Sie einen bestimmten Grund, kein spöttisches Framework zu verwenden? –

+0

Im Allgemeinen keine spezifischen Gründe. Wir stellen es vielleicht vor, aber es ist unwahrscheinlich, dass wir es für diese spezielle Aufgabe über Nacht tun werden. Nehmen wir an, wir möchten jetzt eine Lösung finden, ohne ein spöttisches Framework hinzuzufügen. – FOR

4

Ich löste dies durch eine Schnittstelle IDataServiceQuery mit zwei Implementierungen zu erstellen:

  • DataServiceQueryWrapper
  • MockDataServiceQuery

ich IDataServiceQuery dann verwenden, wo immer ich ein DataServiceQuery bisher verwendeten haben würde.

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

Die DataServiceQueryWrapper nimmt ein DataServiceQuery in seinem Konstruktor und dann die Delegierten alle Funktionen auf die Abfrage übergibt. Auch die MockDataServiceQuery nimmt ein IQueryable und Delegierten alles, was es auf die Abfrage kann.

Für die Mock IDataServiceQuery Methoden, die ich gerade zurück this, obwohl Sie etwas tun können, um die Funktionalität zu verspotten, wenn Sie möchten.

Zum Beispiel:

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