EF4 -虚拟存储库中的导航功能

本文关键字:导航 功能 虚拟存储 EF4 | 更新日期: 2023-09-27 18:06:31

我已经为我的EF存储库创建了一个假库,其方式类似于Julie Lerman关于EF4模拟和单元测试的博客文章。

我的问题是如何获得假存储库来处理表之间的关系?

假设我有两个包含Customers和Orders的表。它们之间存在1对多的关系,因此一个客户可以有多个订单。

我的假存储库将被设置为:

public class FakeMyRepository : IMyRepository
{
  public FakeMyRepository()
  {
    Committed = false;
    FillCustomers();
    FillOrders();
  }
  public bool Committed { get; set; }
  public System.Data.Objects.IObjectSet<Customer> Customers { get; set; }
  public System.Data.Objects.IObjectSet<Order> Orders { get; set; }
  public void Commit()
  {
    Committed = true;
  }
  private void FillCustomers()
  {
    var data = new List<Customer>()
    {
      new Customer() { Id = 1, Name = "Jeff" },
      new Customer() { Id = 2, Name = "Brian" }
    }
    this.Customers = new FakeObjectSet<Customer>(data);
  }
  private void FillOrders()
  {
    var data = new List<Order>()
    {
      new Order() { Id = 1, Customer = 1, Value = 100 }
      new Order() { Id = 2, Customer = 2, Value = 200 }
      new Order() { Id = 3, Customer = 1, Value = 300 }
      new Order() { Id = 4, Customer = 2, Value = 400 }
      new Order() { Id = 5, Customer = 1, Value = 500 }
    }
    this.Orders = new FakeObjectSet<Order>(data);
  }
}

如果我的测试是这样的,它通过了:

[TestMethod]
public void FindUserByIdTest()
{
  var repo = new FakeMyRepository();
  var target = new CustomerService(repo);
  var actual = target.GetCustomerById(1);
  Assert.IsNotNull(actual);
  Assert.AreEqual<string>("Jeff",actual.Name);
}

但是如果我想说订单的计数那么它就失败了

[TestMethod]
public void FindUserByIdWithOrderCount()
{
  var repo = new FakeMyRepository();
  var target = new CustomerService(repo);
  var actual = target.GetCustomerById(1);
  Assert.IsNotNull(actual);
  Assert.AreEqual<int>(3,actual.Orders.Count());
}
谁能给我指一下正确的方向吗?

欢呼。

EF4 -虚拟存储库中的导航功能

您的假存储库必须返回填充了订单导航属性的客户。无论如何,这是典型的场景,对单元测试来说没有意义,因为急切加载或延迟加载都是持久性层抽象的泄漏。急切加载(Include)只适用于链接到实体,而惰性加载完全发生在测试代码之外。

顺便说一句。关于单元测试和实体框架的一些东西

当您请求actual.Orders.Count()时失败的原因是actual返回您之前创建的这个对象:

new Customer() { Id = 1, Name = "Jeff" }

这个具体的Customer对象的Orders属性是null,因为你从来没有设置过它。

这样看。就代码而言,存储库只是对象的一种存储机制。它不关心存储是否由数据库中具有这些表之间关系的表支持,或者是否只是放在某个列表中。重要的是,当您调用repo.GetCustomer(1)时,它将返回一个ID为1的Customer对象,并按原样填写所有其他详细信息。在这种情况下,您需要相关订单实际位于Customer对象中!

你可以这样做:

private void FillData()
{
    var customer1 = new Customer() { Id = 1, Name = "Jeff" };
    var customer2 = new Customer() { Id = 2, Name = "Brian" };
    var order1 = new Order() { Id = 1, Customer = 1, Value = 100 };
    var order2 = new Order() { Id = 2, Customer = 2, Value = 200 };
    var order3 = new Order() { Id = 3, Customer = 1, Value = 300 };
    customer1.Orders = new List<Order> {order1, order3};
    customer2.Orders = new List<Order> {order2};
    this.Customers = new FakeObjectSet<Customer>(new[] {customer1, customer2});
    this.Orders = new FakeObjectSet<Order>(new[] {order1, order2, order3});
}

但是你的全套订单。

话虽如此,我强烈建议不要使用这样的手工混凝土模型。看看使用Moq框架:http://code.google.com/p/moq/它会让你的生活更容易。

Edit:您可能会发现在为单元测试构建上下文时有些东西很有用。首先,如果您不知道什么是扩展方法,这只是一个示例:

namespace Foo
{
    public static class StringExtensions
    {
        public static bool IsNullOrEmpty(this string input)
        {
            return string.IsNullOrEmpty(input);
        }
    }
}

消费者可以…

using Foo;
string a = null, b = "hello";
a.IsNullOrEmpty();  // returns true
b.IsNullOrEmpty();  // returns false

语法允许您创建可以调用的方法,就像它们是对象上的实例方法一样,但实际上是在其他地方定义的静态方法。

现在,话虽如此。您可能会创建一些扩展方法和helper类来为单元测试构建上下文。例如:

public static class UnitTestHelper
{
    private static int _nextCustomerId = 0;
    private static int _nextOrderId = 0;
    public static Customer MockCustomer(string name)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentException("name");
        var id = _nextCustomerId;
        _nextCustomerId += 1;
        return new Customer
            {
                Id = id,
                Name = name,
                Orders = new List<Order>()
            };
    }
    public static Customer WithOrder(this Customer customer, int value)
    {
        if (customer == null) throw new ArgumentNullException("customer");
        var order = new Order
            {
                Id = _nextOrderId,
                Customer = customer.Id,
                Value = value
            };
        customer.Orders.Add(order);
        _nextOrderId += 1;
        return customer;
    }
    public static Mock<Repository> HavingCustomers(this Mock<Repository> repository,
                                                   params Customer[] customers)
    {
        if (repository == null) throw new ArgumentNullException("repository");
        var allOrders = customers.SelectMany(c => c.Orders);
        repository.Setup(r => r.Customers)
                  .Returns(new FakeObjectSet<Customer>(customers));
        repository.Setup(r => r.Orders)
                  .Returns(new FakeObjectSet<Order>(allOrders));
        return repository;
    }
}

一旦你有了这个,而不是做很多艰苦的手工创建的东西,你可以做一些像…

[Test]
public void ShouldReturnAllCustomersWithoutOrders()
{
    var john = UnitTestHelper.MockCustomer("John").WithOrder(100).WithOrder(200);
    var paul = UnitTestHelper.MockCustomer("Paul");
    var george = UnitTestHelper.MockCustomer("George").WithOrder(15);
    var ringo = UnitTestHelper.MockCustomer("Ringo");
    var mockRepository = new Mock<Repository()
        .HavingCustomers(john, paul, george, ringo);
    var custServ = new CustomerService(mockRepository.Object);
    var customersWithoutOrders = custServ.GetCustomersWithoutOrders();
    Assert.That(customersWithoutOrders.Count(), Is.EqualTo(2));
    Assert.That(customersWithoutOrders, Has.Member(paul));
    Assert.That(customersWithoutOrders, Has.Member(ringo));
}

如果要在多个测试中使用该设置,则可以将该设置提取到附带SetUpAttribute的方法中。

当你为你的单元测试定义上下文时,你想要尽可能多的灵活性,你不想假设对于你写的每个单元测试,你总是想要相同的两个客户,相同的八个订单。但这并不意味着你不能编写一些快速的帮助方法或类,使设置更容易,更少的冗长。

希望有帮助!

很简单。只在初始化客户

时填充订单
private void FillCustomers() 
  { 
    var data = new List<Customer>() 
    { 
      new Customer
      { 
        Id = 1, 
        Name = "Jeff",
        Orders=new List<Order>(new []
        {
           new Order() { Id = 1, Customer = 1, Value = 100 }  
           new Order() { Id = 3, Customer = 1, Value = 300 }  
           new Order() { Id = 5, Customer = 1, Value = 500 }            
        }
      }, 
      new Customer() { Id = 2, Name = "Brian" } 
    } 
    this.Customers = new FakeObjectSet<Customer>(data); 
  } 

现在你的测试应该通过了。