2009-09-17 3 views
5

Как я могу издеваться над DataServiceQuery для целей модульного тестирования?Mocking DataServiceQuery <TElement>

Длинные детали следуют: Представьте себе приложение 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 просто потому, что это тип данных, мы обнаружили, в автогенерированном классе 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; 
     } 
    } 
    } 
} 

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

ответ

0

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

Рассматривали ли вы с помощью насмешливый рамки?

Вы можете использовать Typemock Изолятор создать фальшивый экземпляр DataServiceQuery:

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

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

+0

Дрор, спасибо за идею, но на данный момент мы не используем какие-либо насмешливые рамки. Нам было бы интересно увидеть, есть ли решение, которое не полагается на него. Тем не менее, спасибо – FOR

+0

У вас есть конкретная причина не использовать насмешливую структуру? –

+0

В общем, нет особых причин. Мы можем представить его, но вряд ли мы сделаем это за одну ночь для этой конкретной задачи. Итак, скажем, на данный момент мы хотели бы найти решение, не добавляя насмешливую структуру. – FOR

4

Я решил это путем создания интерфейса 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; 
}