Как можно смоделировать 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 пытается получить количество элементов в моделях.
Заранее спасибо.