如何模拟DataServiceQuery以进行单元测试?
详细信息如下: 想象一下ASP.NET MVC应用程序,其中控制器与ADO.NET DataService对话,封装了我们模型的存储(例如,我们将阅读Customers列表)。通过对服务的引用,我们得到一个继承自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实例的构造函数,以便我们可以在单元测试中使用mock:
[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 { /* ??? */ } }
}
在Customers getter中,我们想要实例化一个DataServiceQuery实例,用CustomersToReturn中的Customers填充它,然后返回它。我遇到的问题:
1~ DataServiceQuery没有公共构造函数;要实例化一个你应该在DataServiceContext上调用CreateQuery;见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试图获取模型中的元素数量,也会触发相同的错误。 提前谢谢。
答案 0 :(得分: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;
}
答案 1 :(得分:0)
[免责声明 - 我在Typemock工作]
您是否考虑过使用模拟框架?
您可以使用Typemock Isolator创建一个虚假的DataServiceQuery实例:
var fake = Isolate.Fake.Instance<DataServiceQuery>();
你可以创建一个类似的伪造DataServiceContext并设置它的行为,而不是试图继承它。