Zombando de um DataServiceQuery
-
10-07-2019 - |
Pergunta
Como posso zombar um DataServiceQuery para fins de teste de unidade?
Long Detalhes seguir: Imagine um aplicativo ASP.NET MVC, onde as conversações controlador a um ADO.NET DataService que encapsula o armazenamento dos nossos modelos (por exemplo, causa nós estaremos lendo uma lista de clientes). Com uma referência ao serviço, temos uma classe gerada herdando 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 */
}
}
O controlador poderia ser:
namespace Sample.Controllers
{
public class CustomerController : Controller
{
private IMyDataContext context;
public CustomerController(IMyDataContext context)
{
this.context=context;
}
public ActionResult Index() { return View(context.Customers); }
}
}
Como você pode ver, eu usei um construtor que aceita uma instância IMyDataContext para que possamos utilizar um mock em nossa unidade de teste:
[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 necessidade de implementar a mesma interface de IMyDataContext:
namespace Sample.Services
{
public interface IMyDataContext
{
DataServiceQuery<Customer> Customers { get; }
/* and more */
}
}
No entanto, quando tentar implementar a classe MockContext, nos deparamos com problemas devido à natureza de DataServiceQuery (que, para ser claro, estamos usando na interface IMyDataContext simplesmente porque esse é o tipo de dados que encontrámos no auto classe MyDataContext -generated que começou com). Se tentarmos escreve:
public class MockContext : IMyDataContext
{
public IList<Customer> CustomersToReturn { set; private get; }
public DataServiceQuery<Customer> Customers { get { /* ??? */ } }
}
Nos Clientes getter gostaríamos de instanciar uma instância DataServiceQuery, preenchê-lo com os Clientes em CustomersToReturn, e devolvê-lo. Os problemas que eu funciono em:
1 ~ DataServiceQuery não tem nenhum construtor público; Para instanciar um que você deve chamar CreateQuery em um DataServiceContext; consulte MSDN
2 ~ Se eu fizer a herdar MockContext de DataServiceContext bem, e chamar CreateQuery para obter um DataServiceQuery para uso, o serviço ea consulta tem que ser amarrado a uma URI válida e, quando tento para iterar ou acessar os objetos na consulta, ele vai tentar e executar contra esse URI. Em outras palavras, se eu mudar o MockContext como tal:
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;
}
}
}
}
Em seguida, no teste de unidade, teremos um erro na linha marcada como linha A, porque http: // www .contoso.com não hospeda o nosso serviço. O mesmo erro é acionado, mesmo se uma linha tenta fazer com que o número de elementos em modelos. Agradecemos antecipadamente.
Solução
[Disclaimer - Eu trabalho em Typemock]
Você já pensou em usar um quadro zombando?
Você pode usar Typemock Isolador para criar uma instância falsa de DataServiceQuery:
var fake = Isolate.Fake.Instance<DataServiceQuery>();
E você pode criar um falso semelhante DataServiceContext e configurá-lo de comportamento em vez de tentar herdá-lo.
Outras dicas
Eu resolvi isso criando um IDataServiceQuery
de interface com duas implementações:
-
DataServiceQueryWrapper
-
MockDataServiceQuery
Eu, então, usar IDataServiceQuery
onde quer que eu teria usado anteriormente um 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);
}
O DataServiceQueryWrapper
leva um DataServiceQuery
em sua delegados construtor e, em seguida, toda a funcionalidade para a consulta passado. Da mesma forma, o MockDataServiceQuery
leva um tudo IQueryable
e delegados que pode para a consulta.
Para os métodos IDataServiceQuery
simulados, eu atualmente apenas this
voltar, embora você poderia fazer algo para zombar a funcionalidade se você quiser.
Por exemplo:
// (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;
}