Перемешивание DataServiceQuery <TElement> - PullRequest
5 голосов
/ 17 сентября 2009

Как можно смоделировать DataServiceQuery для модульного тестирования?

Далее следуют подробности: Представьте себе приложение ASP.NET MVC, в котором контроллер взаимодействует с ADO.NET DataService, который инкапсулирует хранилище наших моделей (например, ради того, чтобы мы читали список клиентов). Со ссылкой на сервис мы получаем сгенерированный класс, наследуемый от 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 */
  }
}

Контроллер может быть:

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

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

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

Как видите, я использовал конструктор, который принимает экземпляр IMyDataContext, чтобы мы могли использовать макет в нашем модульном тесте:

[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 и MyDataContext должны реализовывать один и тот же интерфейс IMyDataContext:

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

Однако, когда мы пытаемся реализовать класс MockContext, мы сталкиваемся с проблемами из-за природы DataServiceQuery (который, как понятно, мы используем в интерфейсе IMyDataContext просто потому, что это тип данных, который мы нашли в auto класс MyDataContext, с которого мы начали). Если мы попытаемся написать:

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

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

В получателе клиентов мы хотели бы создать экземпляр DataServiceQuery, заполнить его клиентами в CustomersToReturn и вернуть его. Проблемы, с которыми я сталкиваюсь:

1 ~ DataServiceQuery не имеет открытого конструктора; чтобы создать его, вы должны вызвать CreateQuery для DataServiceContext; см MSDN

2 ~ Если я сделаю так, чтобы MockContext наследовал и от DataServiceContext, и вызвал CreateQuery, чтобы получить DataServiceQuery для использования, то сервис и запрос должны быть привязаны к действительному URI, а при попытке итерируйте или обращайтесь к объектам в запросе, он попытается выполнить этот URI. Другими словами, если я изменю MockContext следующим образом:

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

Затем в модульном тесте мы получаем ошибку в строке, помеченной как LINE A, потому что http://www.contoso.com не обслуживает наш сервис. Та же ошибка возникает, даже если LINE A пытается получить количество элементов в моделях. Заранее спасибо.

Ответы [ 2 ]

4 голосов
/ 14 апреля 2010

Я решил это, создав интерфейс IDataServiceQuery с двумя реализациями:

  • DataServiceQueryWrapper
  • MockDataServiceQuery

Затем я использую IDataServiceQuery везде, где я бы ранее использовал 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 принимает DataServiceQuery в своем конструкторе, а затем делегирует все функциональные возможности переданному запросу. Аналогично, MockDataServiceQuery принимает IQueryable и делегирует все, что может, запросу.

Для фиктивных IDataServiceQuery методов я в настоящее время просто возвращаю this, хотя вы могли бы сделать что-то, чтобы высмеивать функциональность, если хотите.

Например:

// (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;
}
0 голосов
/ 17 сентября 2009

[Отказ от ответственности - я работаю в Typemock]

Рассматривали ли вы использование насмешливого фреймворка?

Вы можете использовать Typemock Isolator для создания поддельного экземпляра DataServiceQuery:

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

И вы можете создать аналогичный фальшивый DataServiceContext и установить его поведение вместо того, чтобы пытаться его наследовать.

...