如何在单元测试中使用Moq和DbFunction来防止NotSupportedException?

Lyi*_*ise 27 c# unit-testing entity-framework moq

我目前正在尝试对通过实体框架运行的查询运行一些单元测试.查询本身在实时版本上运行没有任何问题,但单元测试总是失败.

我把它缩小到我对DbFunctions.TruncateTime的使用,但我不知道如何通过这种方式来获得单元测试以反映实时服务器上发生的情况.

这是我正在使用的方法:

    public System.Data.DataTable GetLinkedUsers(int parentUserId)
    {
        var today = DateTime.Now.Date;

        var query = from up in DB.par_UserPlacement
                    where up.MentorId == mentorUserId
                        && DbFunctions.TruncateTime(today) >= DbFunctions.TruncateTime(up.StartDate)
                        && DbFunctions.TruncateTime(today) <= DbFunctions.TruncateTime(up.EndDate)
                    select new
                    {
                        up.UserPlacementId,
                        up.Users.UserId,
                        up.Users.FirstName,
                        up.Users.LastName,
                        up.Placements.PlacementId,
                        up.Placements.PlacementName,
                        up.StartDate,
                        up.EndDate,
                    };

        query = query.OrderBy(up => up.EndDate);

        return this.RunQueryToDataTable(query);
    }
Run Code Online (Sandbox Code Playgroud)

如果我注释掉带有DbFunctions的行,则测试全部通过(除了检查只运行给定日期的有效结果的那些).

有没有办法可以提供在这些测试中使用的DbFunctions.TruncateTime的模拟版本?本质上它应该只返回Datetime.Date,但在EF查询中不可用.

编辑:这是使用日期检查失败的测试:

    [TestMethod]
    public void CanOnlyGetCurrentLinkedUsers()
    {
        var up = new List<par_UserPlacement>
        {
            this.UserPlacementFactory(1, 2, 1), // Create a user placement that is current
            this.UserPlacementFactory(1, 3, 2, false) // Create a user placement that is not current
        }.AsQueryable();

        var set = DLTestHelper.GetMockSet<par_UserPlacement>(up);

        var context = DLTestHelper.Context;
        context.Setup(c => c.par_UserPlacement).Returns(set.Object);

        var getter = DLTestHelper.New<LinqUserGetLinkedUsersForParentUser>(context.Object);

        var output = getter.GetLinkedUsers(1);

        var users = new List<User>();
        output.ProcessDataTable((DataRow row) => students.Add(new UserStudent(row)));

        Assert.AreEqual(1, users.Count);
        Assert.AreEqual(2, users[0].UserId);
    }
Run Code Online (Sandbox Code Playgroud)

编辑2:这是来自相关测试的消息和调试跟踪:

Test Result: Failed

Message: Assert.AreEqual failed. Expected:<1>. Actual:<0>

Debug Trace: This function can only be invoked from LINQ to Entities
Run Code Online (Sandbox Code Playgroud)

从我读到的,这是因为没有这个方法的LINQ to Entities实现可以在这个地方用于单元测试,尽管有实时版本(因为它查询SQL服务器).

est*_*art 24

我知道我已经迟到了,但一个非常简单的解决方法就是编写自己的方法,该方法使用DbFunction属性.然后使用该函数而不是DbFunctions.TruncateTime.

[DbFunction("Edm", "TruncateTime")]
public static DateTime? TruncateTime(DateTime? dateValue)
{
    return dateValue?.Date;
}
Run Code Online (Sandbox Code Playgroud)

使用此函数将在Linq to Entities使用时执行EDM TruncateTime方法,否则将运行提供的代码.

  • 当然,这是我见过的最简单,最快捷的解决方案,让我们围绕这个恼人的问题.从长远来看,我们认为我们可能会尽可能尝试Dapper而不是EF,因为它更简单(假设我们可以根据所需的功能). (2认同)
  • @JohnOsborne 同样的问题!“Edm”是 MS 为各种实体框架 DB 函数选择的命名空间,尽管我知道它代表什么。“DbFunctions”的这个页面提到了很多 EDM,但我仍然没有很好地解释该命名空间的确切原因以及如何工作。https://msdn.microsoft.com/en-us/library/system.data.entity.dbfunctions(v=vs.113).aspx (2认同)
  • 我添加了方法,但在我的LINQ中留下了DbFunctions.TruncateTime调用 - 这就是为什么它没有工作:)我正在调用我的静态方法(带属性),它现在正在工作. (2认同)

Lyi*_*ise 16

感谢所有人的帮助,我在阅读了qujck提到的垫片后,设法找到了一个对我有用的解决方案.添加一个假的EntityFramework程序集后,我可以通过将它们更改为以下来修复这些测试:

[TestMethod]
public void CanOnlyGetCurrentLinkedUsers()
{
    using (ShimsContext.Create())
    {
        System.Data.Entity.Fakes.ShimDbFunctions.TruncateTimeNullableOfDateTime =
            (DateTime? input) =>
            {
                return input.HasValue ? (DateTime?)input.Value.Date : null;
            };

        var up = new List<par_UserPlacement>
        {
            this.UserPlacementFactory(1, 2, 1), // Create a user placement that is current
            this.UserPlacementFactory(1, 3, 2, false) // Create a user placement that is not current
        }.AsQueryable();

        var set = DLTestHelper.GetMockSet<par_UserPlacement>(up);

        var context = DLTestHelper.Context;
        context.Setup(c => c.par_UserPlacement).Returns(set.Object);

        var getter = DLTestHelper.New<LinqUserGetLinkedUsersForParentUser>(context.Object);

        var output = getter.GetLinkedUsers(1);
    }

    var users = new List<User>();
    output.ProcessDataTable((DataRow row) => users.Add(new User(row)));

    Assert.AreEqual(1, users.Count);
    Assert.AreEqual(2, users[0].UserId);
}
Run Code Online (Sandbox Code Playgroud)