2011-08-23 61 views
0

我以類似於Julie Lerman's blog post on EF4 mocks and unit tests的方式爲我的EF存儲庫創建了一個僞造品。EF4 - 假庫中的導航功能?

我的問題是我怎樣才能得到假存儲​​庫來處理表之間的關係?

說我有兩張客戶和訂單表。他們之間有一對多的關係,這樣一個客戶可以有多個訂單。

我的假庫將成立類似:

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()); 
} 

任何人都可以爲我指出正確的方向嗎?

乾杯。

回答

1

您的虛假資料庫必須返回訂單導航屬性已滿的客戶。無論如何,這是典型的場景,它對單元測試沒有意義,因爲急切或懶惰加載都是對持久層的抽象抽象。急切的加載(Include)只能與linq-to-entities一起使用,並且延遲加載完全在測試代碼之外發生。

Btw。一些關於unit testing and Entity framework

+0

我沒有測試存儲庫,我正在測試服務方法。在我的例子中,它是一個GetCustomerById()函數,但它可能涉及更多的東西。我只是想看看是否可以構建假回購,否定我不必爲每個單元測試編寫大量模擬代碼。 – Nick

+0

@Nick:是的,假的回購可以這樣構建,但每次都會返回關係**,所以你不會測試正確的代碼。 –

0

很容易。當您初始化客戶時,只需填寫訂單

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); 
    } 

現在您的測試應通過。

1

當你問actual.Orders.Count()失敗的原因是因爲actual返回此對象,它前面創建:

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

這具體Customer對象有nullOrders財產,因爲你永遠不設置它。

看這個吧。就您的代碼而言,存儲庫只是對象的存儲機制。它並不關心存儲是否由數據庫中的表支持,並且這些表之間存在關係,或者只是放在某個列表中。重要的是,當您撥打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/它會讓你的生活變得更輕鬆。

編輯:這裏有些東西你可能會發現建立你的單元測試的上下文有用。首先,如果你不知道什麼擴展的方法是,這僅僅是一個例子:

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 

的語法允許你創建可以稱爲的方法好像是它們是對象上的實例方法,但實際上是在別處定義的靜態方法。

現在,這是說。你可能會創建一些擴展方法和幫助類來爲單元測試構建上下文。舉個例子。

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的方法中。

你會想盡可能多的靈活性,儘可能當你定義你的單元測試的情況下,你不想假設爲單元測試,你寫你總是想的一樣兩個客戶有八個相同的訂單。但這並不意味着你不能編寫一些快速幫助器方法或類來使設置更簡單,更簡潔。

希望有幫助!

+0

我在一堆地方使用Moq,但我試圖避免在以某種方式觸及存儲庫的每個單元測試中加載設置代碼:\ – Nick

+0

不幸的是,這就是單元測試的本質。每個特定的單元測試都會說:「在這種情況下,考慮到這些方法調用,我應該得到這個結果。」所以你需要先設置環境。 *通常*只設置您期望的上下文是個好主意。我要添加一個可以幫助你的編輯。 – BishopRook