【问题标题】:Mocking a DataServiceQuery<TElement>模拟 DataServiceQuery<TElement>
【发布时间】:2009-09-17 14:18:36
【问题描述】:

如何模拟 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 { /* ??? */ } }
}

在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 尝试获取模型中的元素数量,也会触发相同的错误。 提前致谢。

【问题讨论】:

    标签: asp.net-mvc mocking wcf-data-services


    【解决方案1】:

    我通过创建具有两个实现的接口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;
    }
    

    【讨论】:

      【解决方案2】:

      [免责声明 - 我在 Typemock 工作]

      您是否考虑过使用模拟框架?

      您可以使用 Typemock Isolator 创建一个假的 DataServiceQuery 实例:

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

      您可以创建一个类似的假 DataServiceContext 并设置它的行为,而不是尝试继承它。

      【讨论】:

      • Dror,感谢您的想法,但目前我们没有使用任何模拟框架。我们很想看看是否有一个不依赖于一个的解决方案。不过,谢谢你
      • 您是否有特定的理由不使用模拟框架?
      • 一般来说,没有具体原因。我们可能会介绍它,但我们不可能在一夜之间完成这项特定任务。所以,假设现在我们想在不添加模拟框架的情况下找到解决方案。
      • 将其标记为答案,因为团队决定使用模拟框架(最终)
      • @FOR 我很高兴听到你已经成功了 - 如果你需要任何帮助,请告诉我。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      相关资源
      最近更新 更多