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());
}
谁能给我指一下正确的方向吗?欢呼。
您的假存储库必须返回填充了订单导航属性的客户。无论如何,这是典型的场景,对单元测试来说没有意义,因为急切加载或延迟加载都是持久性层抽象的泄漏。急切加载(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);
}
现在你的测试应该通过了。