2013-12-12 51 views
1

我想單元測試一個採用FormCollection並具有Find方法的控制器。我已成功實施假冒DbContextFakeDbSet(從here),以便我可以在我的測試中使用假物件。在FakeDbSet實現中僞造find方法

我想測試的具體方法是這樣的:

// 
    // POST: /Order/SettleOrders 

    [HttpPost] 
    [ValidateAntiForgeryToken] 
    public ActionResult SettleOrders(FormCollection c) 
    { 
     int i = 0; 
     if (ModelState.IsValid) 
     { 
      var ordersCollection = new List<Order>(); 

      // Get values of form 
      var OrderIdArray = c.GetValues("item.OrderId"); 
      var UnitPriceArray = c.GetValues("item.UnitPrice"); 

      for (i = 0; i < OrderIdArray.Count(); i++) 
      { 
       // Find order in database and update the unitprice and isconfirmed status 
       Order order = db.Orders.Find(Convert.ToInt32(OrderIdArray[i])); 
       order.UnitPrice = Convert.ToDecimal(UnitPriceArray[i]); 
       order.IsConfirmed = true; 
       db.SetModified(order); 
       ordersCollection.Add(order); 
      } 
      db.SaveChanges(); 
     } 

     // Return orders of this date to view 
     var currentDate = DateTime.Today; 
     var orders = db.Orders.Include(o => o.Product) 
           .Include(o => o.User) 
           .Where(o => o.Date == currentDate); 
     return View("Confirmation", orders.ToList().OrderBy(o => o.User.Name)); 
    } 

這是我建立我的OrderController的測試使用Moq

[TestInitialize] 
    public void OrderControllerTestInitialize() 
    { 
     // Arrange 
     var unconfirmedMemoryItems = new FakeOrderSet 
     { 
      // @TODO Tests/methods should ideally not be dependent on DateTime.Today... 
      new Order { OrderId = 1, UnitPrice = 1.00M, Quantity = 2, Date = DateTime.Today, IsConfirmed = false }, 
      new Order { OrderId = 2, UnitPrice = 2.00M, Quantity = 1, Date = DateTime.Today, IsConfirmed = false } 
     }; 

     // Create mock unit of work 
     var unconfirmedMockData = new Mock<ISeashellBrawlContext>(); 
     unconfirmedMockData.Setup(m => m.Orders).Returns(confirmedMemoryItems); 

     // Setup controller 
     unconfirmedOrderController = new OrderController(confirmedMockData.Object); 
    } 

然後測試去喜歡這確認未經確認的訂單正在確認。

[TestMethod] 
    public void TestSettleOrdersPost() 
    { 
     // Invoke 
     FormCollection form = CreatesettleOrdersPostFormCollection(); 
     var viewResult = unconfirmedOrderController.SettleOrders(form) as ViewResult; 
     var ordersFromView = (IEnumerable<Order>)viewResult.Model; 

     // Assert 
     Assert.AreEqual(3, ordersFromView.ElementAt(0).Quantity, 
      "New item should be added to older one since it has same index and is of same date"); 
     Assert.AreEqual(true, ordersFromView.ElementAt(0).IsConfirmed, 
      "The item should also be set to confirmed"); 
    } 

    // Helper methods 

    private static FormCollection CreatesettleOrdersPostFormCollection() 
    { 
     FormCollection form = new FormCollection(); 
     form.Add("item.OrderId", "1"); 
     form.Add("item.UnitPrice", "2.00"); 
     form.Add("item.OrderId", "2"); 
     form.Add("item.UnitPrice", "3.00"); 
     return form; 
    } 

不幸的是我得到了以下錯誤消息:

測試名稱:TestSettleOrdersPost 結果消息:

System.NullReferenceException:未設置爲一個對象的實例 對象引用。結果堆棧跟蹤:在 Controllers.OrderController.SettleOrders(的FormCollection三) 控制器\ OrderController.cs:行121

這可能與假Find方法沒有做它應該做的事。我使用的查找方法如下所示:

class FakeOrderSet : FakeDbSet<Order> 
{ 
    public override Order Find(params object[] keyValues) 
    { 
     var id = Convert.ToInt32(keyValues[0]); 
     Order o = null; 

     IQueryable<Order> keyQuery = this.AsQueryable<Order>(); 
     foreach (var order in keyQuery) 
     { 
      if (order.OrderId == id) 
      { 
       o = order; 
      } 
     } 

     return o; 
    } 
} 

我不知道如何改進此代碼。看着它,我確信它應該起作用。因爲我是單元測試的7天noobot,但這個信念並沒有多大價值。我希望有人能幫助我。

+0

_「這可能需要做......」在121行之前放置一個斷點,看看會發生什麼。 – CodeCaster

+0

@CodeCaster我在做單元測試時如何在代碼中放置斷點?我無法做到這一點,我也不能寫信給控制檯......讓這些工具可用將是一個巨大的幫助。我找到了一個答案:'Debug' - >'在當前上下文中測試'(或其他) –

+0

請參閱http:// stackoverflow。com/questions/12594018/can-debug-a-unit-testing-project-in-visual-studio-2012 and http://stackoverflow.com/questions/4103712/stepping-through-and-debugging-code-in-單元測試。在測試資源管理器中右鍵單擊該測試,如果其他方式不起作用,請單擊「調試」。 – CodeCaster

回答

2

我認爲您可以在您的通用FakeDbSet<T>類中包含Find方法實現。 例如,如果所有的entites擁有財產性Id映射到數據庫中的主鍵,你可以做以下操作:

public class FakeDbSet<T> 
{ 
    .... 

    public T Find(params object[] keyvalues) 
    { 
    var keyProperty = typeof(T).GetProperty(
      "Id", 
      BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); 
    var result = this.SingleOrDefault(obj => 
     keyProperty.GetValue(obj).ToString() == keyValues.First().ToString()); 
    return result; 
    } 
} 

如果你的主要屬性是不是Id你可以嘗試確定某些algorith如何識別關鍵屬性通過反射實體(例如,如果Order類具有OrderId關鍵屬性,您可以嘗試通過反射獲取類名並使用「Id」字符串連接它來確定它)。

+0

是的,這是一個選項,然後也讓你所有的假對象得到分配一個ID。儘管我沒有得到這個工作。幸運的是,我發現錯誤是什麼(我沒有發佈一些與之相關的代碼,因爲我認爲它不相關),我的方法確實工作,因爲我也應該初始化用戶FakeDbSet,因爲它正在寫一些東西給那個!畢竟,Find方法正在工作。 –

1

我有同樣的問題:

public override TEntity Find(params object[] keyValues) 
    { 
     if (keyValues.Length == 0) 
      throw new ArgumentNullException("keyValues"); 
     return this.SingleOrDefault(GenerateKeyFilter(keyValues)); 
    } 

    private Expression<Func<TEntity, bool>> GenerateKeyFilter(object[] keyValues) 
    { 
     var conditions = new List<BinaryExpression>(); 
     var objectParam = Expression.Parameter(typeof(TEntity)); 

     var keyFields = !!!Helper.KeyFields<TEntity>(); 

     if (keyFields.Count != keyValues.Length) 
      throw new KeyNotFoundException(); 
     for (var c = 0; c < keyFields.Count; c++) 
      conditions.Add(Expression.MakeBinary(
       ExpressionType.Equal, 
       Expression.MakeMemberAccess(objectParam, keyFields[c]), 
       Expression.Constant(keyValues[c], keyFields[c].PropertyType) 
       )); 
     var result = conditions[0]; 
     for (var n = 1; n < conditions.Count; n++) 
      result = Expression.And(result, conditions[n]); 
     return Expression.Lambda<Func<TEntity, bool>>(result, objectParam); 
    } 

這種運作良好,但是,要創建我不得不重新配置模式和手動設置鍵,類似於使用EntityTypeConfiguration的測試。