2009-09-17 7 views
5

Come posso simulare un DataServiceQuery per lo scopo di test dell'unità?Mocking di un DataServiceQuery <TElement>

lunghe articolate come segue: Immaginate un'applicazione ASP.NET MVC, in cui i colloqui controller a una DataService ADO.NET che incapsula l'archiviazione dei nostri modelli (ad esempio l'amor saremo leggendo un elenco di clienti). Con un riferimento al servizio, otteniamo una classe generata eredita da 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 */ 
    } 
} 

Il controller potrebbe essere:

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

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

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

Come potete vedere, ho usato un costruttore che accetta un'istanza IMyDataContext così che possiamo usare un modello nel nostro test di unità:

[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 e MyDataContext hanno bisogno di implementare la stessa interfaccia di IMyDataContext:

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

Tuttavia, quando cerchiamo di implementare la classe MockContext, ci imbattiamo in problemi a causa della natura di DataServiceQuery (che, per essere chiari, stiamo usando nell'interfaccia IMyDataContext semplicemente perché questo è il tipo di dati che abbiamo trovato nella classe MyDataContext generata automaticamente che abbiamo iniziato con). Se proviamo a scrivere:

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

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

Negli Clienti getter vorremmo istanziare un'istanza DataServiceQuery, popolarlo con i clienti in CustomersToReturn, e restituirlo. I problemi che ho incontrato:

1 ~ DataServiceQuery non ha un costruttore pubblico; per istanziare uno si dovrebbe chiamare CreateQuery su un DataServiceContext; vedi MSDN

2 ~ Se faccio il MockContext ereditare da DataServiceContext pure, e chiamare createQuery per ottenere un DataServiceQuery da utilizzare, il servizio e la richiesta devono essere legato ad un URI valido e, quando provo a iterare o accedere agli oggetti nella query, proverà ed eseguirà contro quell'URI. In altre parole, se cambio la MockContext in quanto tale:

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

Poi, nel test di unità, otteniamo un errore sulla linea contrassegnata come LINE A, perché http://www.contoso.com non ospita il nostro servizio. Lo stesso errore viene attivato anche se LINEA tenta di ottenere il numero di elementi nei modelli. Grazie in anticipo.

risposta

0

[Disclaimer - Io lavoro in Typemock]

Avete pensato di utilizzare un quadro di scherno?

È possibile utilizzare Typemock Isolator per creare un'istanza di falso DataServiceQuery:

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

E si può creare un simile DataServiceContext falso e impostare il suo comportamento invece di cercare di ereditare.

+0

Dror, grazie per l'idea, ma per il momento non stiamo utilizzando qualsiasi quadro di scherno. Saremmo interessati a vedere se esiste una soluzione che non si basa su uno. Ancora, grazie – FOR

+0

Hai una ragione specifica per non usare una struttura di simulazione? –

+0

In generale, nessun motivo specifico. Potremmo introdurlo, ma è improbabile che lo faremo durante la notte per questo specifico compito. Quindi, diciamo che per ora vorremmo trovare una soluzione senza aggiungere una struttura di derisione. – FOR

4

Ho risolto questo creando un'interfaccia IDataServiceQuery con due implementazioni:

  • DataServiceQueryWrapper
  • MockDataServiceQuery

ho quindi utilizzare IDataServiceQuery ovunque avrei precedentemente usato 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); 
} 

Il DataServiceQueryWrapper prende un DataServiceQuery in esso è costruttore e poi delegati tutto funzionalità alla query passata. Analogamente, il MockDataServiceQuery prende un tutto IQueryable e delega possibile per l'interrogazione.

Per i simulati metodi IDataServiceQuery, al momento restituisco solo this, sebbene si possa fare qualcosa per deridere la funzionalità se si desidera.

Ad esempio:

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