Question

Comment puis-je simuler une requête DataServiceQuery à des fins de test unitaire?

Détails longs suivent: Imaginons une application ASP.NET MVC, dans laquelle le contrôleur parle à un ADO.NET DataService qui encapsule le stockage de nos modèles (par exemple, nous allons lire une liste de 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 constater, j'ai utilisé un constructeur qui accepte une instance IMyDataContext afin que nous puissions utiliser une simulation 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 d'implémenter la classe MockContext, nous rencontrons des problèmes dus à la nature de DataServiceQuery (que, pour être clair, nous utilisons dans l'interface IMyDataContext simplement parce que c'est le type de données trouvé dans l'auto classe MyDataContext générée avec laquelle nous avons commencé). Si nous essayons d’écrire:

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

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

Dans le volet Clients, nous souhaitons instancier une instance DataServiceQuery, la remplir avec les clients dans CustomersToReturn et la renvoyer. Les problèmes que je rencontre:

1 ~ DataServiceQuery n'a pas de constructeur public; pour en instancier un, vous devez appeler CreateQuery sur un DataServiceContext; voir MSDN

2 ~ Si je fais en sorte que le MockContext hérite également de DataServiceContext et appelle CreateQuery pour obtenir un DataServiceQuery à utiliser, le service et la requête doivent être liés à un URI valide et, lorsque j'essaie Pour itérer ou accéder aux objets de la requête, il essaiera de s'exécuter sur cet URI. En d'autres termes, 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 rencontrons une erreur sur la ligne marquée LINE A, car http: // www .contoso.com n'héberge pas notre service. La même erreur est déclenchée même si LINE A tente d'obtenir le nombre d'éléments dans les modèles. Merci d'avance.

Était-ce utile?

La solution

[Disclaimer - Je travaille chez Typemock]

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

Vous pouvez utiliser Typemock Isolator pour créer une fausse instance de DataServiceQuery:

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

Et vous pouvez créer un faux DataServiceContext similaire et définir son comportement au lieu d'essayer de l'hériter.

Autres conseils

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

  • DataServiceQueryWrapper
  • MockDataServiceQuery

J'utilise ensuite IDataServiceQuery là où j'aurais précédemment 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);
}

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

Pour les méthodes fictives IDataServiceQuery , je ne renvoie actuellement que this , bien que vous puissiez faire quelque chose pour simuler la fonctionnalité si vous le souhaitez.

Par exemple:

// (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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top