Domanda

Come posso deridere una DataServiceQuery a scopo di test unitario?

Seguono i dettagli lunghi: Immagina un'applicazione ASP.NET MVC, in cui il controller parla con un DataService ADO.NET che incapsula l'archiviazione dei nostri modelli (ad esempio, leggeremo un elenco di Clienti). Con riferimento al servizio, otteniamo una classe generata che 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 puoi vedere, ho usato un costruttore che accetta un'istanza IMyDataContext in modo che possiamo usare un mock 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 devono implementare la stessa interfaccia IMyDataContext:

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

Tuttavia, quando proviamo a implementare la classe MockContext, incontriamo problemi dovuti alla natura di DataServiceQuery (che, per essere chiari, stiamo usando nell'interfaccia IMyDataContext semplicemente perché è il tipo di dati che abbiamo trovato nell'auto -generata classe MyDataContext che abbiamo iniziato con Se proviamo a scrivere:

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

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

Nel getter dei clienti vorremmo creare un'istanza di un'istanza DataServiceQuery, popolarla con i clienti in CustomersToReturn e restituirla. I problemi che incontro:

1 ~ DataServiceQuery non ha un costruttore pubblico; per creare un'istanza, è necessario chiamare CreateQuery su DataServiceContext; vedi MSDN

2 ~ Se faccio ereditare anche MockContext da DataServiceContext e chiamo CreateQuery per ottenere un DataServiceQuery da utilizzare, il servizio e la query devono essere collegati a un URI valido e, quando provo per iterare o accedere agli oggetti nella query, proverà ad eseguire tale URI. In altre parole, se cambio MockContext come 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;
      }
    }
  }
}

Quindi, nel test unitario, viene visualizzato un errore sulla riga contrassegnata come LINEA A, perché http: // www .contoso.com non ospita il nostro servizio. Lo stesso errore viene attivato anche se la LINEA A tenta di ottenere il numero di elementi nei modelli. Grazie in anticipo.

È stato utile?

Soluzione

[Disclaimer - Lavoro a Typemock]

Hai preso in considerazione l'utilizzo di un framework beffardo?

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

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

E puoi creare un DataServiceContext simile e impostarne il comportamento invece di provare a ereditarlo.

Altri suggerimenti

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

  • DataServiceQueryWrapper
  • MockDataServiceQuery

Uso quindi IDataServiceQuery ovunque avrei 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 nel suo costruttore e quindi delega tutte le funzionalità alla query passata. Allo stesso modo, il MockDataServiceQuery accetta un < code> IQueryable e delega tutto il possibile alla query.

Per i falsi metodi IDataServiceQuery , attualmente restituisco questo , sebbene tu possa fare qualcosa per deridere la funzionalità se vuoi.

Ad esempio:

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

& nbsp;

// (in MockDataServiceQuery.cs)
public IDataServiceQuery<TElement> Expand(string path)
{
    return this;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top