【问题标题】:Faking the find method in FakeDbSet implementation在 FakeDbSet 实现中伪造 find 方法
【发布时间】:2013-12-12 08:37:31
【问题描述】:

我想对采用FormCollection 并具有Find 方法的控制器进行单元测试。我已经成功实现了一个假的DbContext 和一个FakeDbSet(来自here),这样我就可以在我的测试中使用假对象。

我要测试的具体方法是这样的:

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

这就是我使用Moq 设置OrderController 测试的方式:

[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 c) 在 Controllers\OrderController.cs:第 121 行

这可能与伪造的Find 方法没有做它应该做的事情有关。我使用的 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 天的单元测试新手,因此这种信念并没有多大价值。我希望有人可以帮助我。

【问题讨论】:

  • "这可能不得不做" ...在第 121 行之前放置一个断点,看看会发生什么。
  • @CodeCaster 在进行单元测试时如何在代码中设置断点?我无法做到这一点,我也无法写信给控制台......拥有这些工具将是一个巨大的帮助。啊,我找到了答案:Debug -> Tests In Current Context(或其他)
  • stackoverflow.com/questions/12594018/…stackoverflow.com/questions/4103712/…。右键单击测试资源管理器中的测试,如果其他方式不起作用,请单击调试。

标签: c# unit-testing visual-studio-2012 moq


【解决方案1】:

我认为您可以在通用 FakeDbSet&lt;T&gt; 类中包含 Find 方法实现。 例如,如果您的所有实体都有属性 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,您可以尝试确定一些算法如何通过反射识别实体的关键属性(例如,如果Order 类具有OrderId 关键属性,您可以尝试通过以下方式确定它通过反射获取类名并将其与“Id”字符串连接)。

【讨论】:

  • 是的,这是一个选项,然后还可以为所有假对象分配一个 id。我没有让这个工作。幸运的是,我发现了错误所在(我没有发布一些与此相关的代码,因为我认为它不相关),我的方法确实有效,因为我还应该初始化一个用户 FakeDbSet,因为它正在写一些东西给那个! Find 方法毕竟是有效的。
【解决方案2】:

我也有同样的问题:

    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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多