C# 如何在entity framework 6中使用Moq对删除操作进行单元测试

C# 如何在entity framework 6中使用Moq对删除操作进行单元测试,c#,.net,entity-framework,unit-testing,moq,C#,.net,Entity Framework,Unit Testing,Moq,*更新编辑-部分解决方案-仍然需要帮助*-我发现异常只是误导。它给了我这个例外,因为我得到了模拟属性被错误调用的次数。它应该被调用两次,而不是一次。那部分现在起作用了 但我仍然不明白为什么该实体没有从列表中删除。是因为它是可查询的吗? 下面的原始问题 我一直在努力学习如何将实体单位化 框架6和6.1 但是,它没有显示如何对删除操作进行单元测试。这是你的电话号码 我正在尝试测试的代码: public void DeleteRequirement(int id) { Requirement

*更新编辑-部分解决方案-仍然需要帮助*-我发现异常只是误导。它给了我这个例外,因为我得到了模拟属性被错误调用的次数。它应该被调用两次,而不是一次。那部分现在起作用了

但我仍然不明白为什么该实体没有从列表中删除。是因为它是可查询的吗?

下面的原始问题

我一直在努力学习如何将实体单位化 框架6和6.1

但是,它没有显示如何对删除操作进行单元测试。这是你的电话号码 我正在尝试测试的代码:

public void DeleteRequirement(int id)
{
    Requirement requirementToDelete = GetRequirement(id);
    context.Requirement.Remove(requirementToDelete);
    context.SaveChanges();
}

public Requirement GetRequirement(int id)
{
    return (from result in context.Requirement
            where result.Id == id
            select result).SingleOrDefault();
}
我的单元测试代码是

[TestMethod]
public void DeleteRequirementSuccessfully()
{
    var requirements = new List<Requirement>
    {
        new Requirement {
            Id = 1,
            Title = "Requirement 1",
            Description = "Requirement 1 description"
        },
        new Requirement {
            Id = 2,
            Title = "Requirement 2",
            Description = "Requirement 2 description"
        },
        new Requirement {
            Id = 3,
            Title = "Requirement 3",
            Description = "Requirement 3 description"
        }
    }
    .AsQueryable();

    var mockDbSet = new Mock<DbSet<Requirement>>();
    var context = new Mock<RequirementsDatabaseEntities>();

    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.Provider)
             .Returns(requirements.Provider);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.ElementType)
             .Returns(requirements.ElementType);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.Expression)
             .Returns(requirements.Expression);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.GetEnumerator())
             .Returns(requirements.GetEnumerator());

    context.Setup(x => x.Requirement).Returns(mockDbSet.Object);

    var dataAccess = new RequirementsDataAccess(context.Object);
    int idToDelete = 1;
    dataAccess.DeleteRequirement(idToDelete);

    context.VerifyGet(x => x.Requirement, Times.Exactly(2)); // <- now verification is correct
    mockDbSet.Verify(x => x.Remove(It.IsAny<Requirement>()), Times.Once());
    context.Verify(x => x.SaveChanges(), Times.Once());
}
如果我注释掉context.VerifyGet行,测试通过,但是 要求不会从列表中删除。有人知道为什么吗

  • 测试失败了
  • 为什么当我注释掉冒犯的一行时,它却通过了要求 尚未删除
  • 为什么这不起作用

  • 它失败是因为您无法模拟非虚拟方法
  • 同样的问题:
    RequirementsDatabaseEntities.Requirement
    不是虚拟方法,因为它在测试方法中提供的输出与您预期的不同。它可能返回空集合

  • 修复:使
    RequirementsDatabaseEntities.Requirement
    getter虚拟因为Moq使用继承来替换方法调用,所以您只能模拟虚拟方法(或接口)


    因此,要么将您试图伪造的方法/属性虚拟化,要么使用隔离器/JustMock等,这些方法使用Jit编织,可以伪造这些方法。

    部分解决方案-我发现异常只是误导。它给了我这个例外,因为我得到了模拟属性被错误调用的次数。它应该被调用两次,而不是一次。那部分现在起作用了。但我仍然不明白为什么该实体没有从名单中删除。这是因为它是可查询的吗?

    首先编辑您对
    需求的定义,使其成为
    列表
    而不是
    可查询的
    ,以便能够模拟添加或删除。并在
    Setup
    方法中使用
    requirements.AsQueryable()

    其次,将此代码添加到mocking remove:

    mockDbSet.Setup(m => m.Remove(It.IsAny<Requirement>())).Callback<Requirement>((entity) => requirements.Remove(entity));
    

    看起来您在某种程度上用单元测试接触数据库,这是您不想做的-单元测试应该单独执行-否则它们将成为集成测试。什么是RequirementsDatabaseEntities?是的,我理解这一点,但无法理解为什么会发生这种情况。我也不知道为什么实体没有被删除。RequirementsDatabaseEntities是DbContextOk,你喜欢怎么做。此错误可能告诉您EF在Web.config文件中找不到名为RequirementsDatabaseEntities的连接字符串。确保在Web.config中有类似的行。还要修改RequirementsDatabaseEntities构造函数,使其看起来像公共RequirementsDatabaseEntities():base(“aspnetdb”){}如果aspnetdb是您的数据库名称,请确保构造函数和连接stringjit中的名称相同。。编织。。听起来很有趣。你有任何描述它的链接/文档吗?我最初的谷歌搜索有点少(它显示了方面编织和运行时编织……不确定这是否相同?)#默认:如果你对这些工具感兴趣,.NET空间目前有三个:Typemock隔离器、Telerik的JustMock和MS Fakes(VS2012/2013)。所有这些都使用.NET profiler API在运行时注入代码,这样就可以在链接中将此方法设置为虚拟时,伪造您想要的任何内容。我认为在使用VS2013 EF 6.1时,这是默认的代码生成模板。我也会得到一个编译时错误吗?在链接中,它将这个方法设置为虚拟的。我认为在使用VS2013 EF 6.1时,这是默认的代码生成模板。还有,我不会得到编译时错误吗?您必须检查生成的代码。如何生成代码的方法不止一种,而且应该有所不同:Legacy、T4、code First,甚至更多。您不会得到错误,因为模拟类型是在运行时构造的,所以编译器不会抱怨。假设新类型只隐藏原始的非虚拟方法,因为您也可以在继承中这样做。这就是为什么我认为你的测试方法提供了真实的属性。我刚刚检查过,它是虚拟的。奇怪的是,当我调试test和context.Requirement时,它会重新运行模拟的DbContext(RequirementsDatabaseEntities)。这很奇怪。
    [TestMethod]
    public void DeleteRequirementSuccessfully()
    {
        var requirements = new List<Requirement>
        {
            new Requirement {
                Id = 1,
                Title = "Requirement 1",
                Description = "Requirement 1 description"
            },
            new Requirement {
                Id = 2,
                Title = "Requirement 2",
                Description = "Requirement 2 description"
            },
            new Requirement {
                Id = 3,
                Title = "Requirement 3",
                Description = "Requirement 3 description"
            }
        };
    
        var mockDbSet = new Mock<DbSet<Requirement>>();
        var context = new Mock<RequirementsDatabaseEntities>();
    
        // You should use .AsQueryable() in these lines
        mockDbSet.As<IQueryable<Requirement>>()
                 .Setup(x => x.Provider)
                 .Returns(requirements.AsQueryable().Provider);
        mockDbSet.As<IQueryable<Requirement>>()
                 .Setup(x => x.ElementType)
                 .Returns(requirements.AsQueryable().ElementType);
        mockDbSet.As<IQueryable<Requirement>>()
                 .Setup(x => x.Expression)
                 .Returns(requirements.AsQueryable().Expression);
        mockDbSet.As<IQueryable<Requirement>>()
                 .Setup(x => x.GetEnumerator())
                 .Returns(requirements.GetEnumerator());
    
        // This line should be added
        mockDbSet.Setup(m => m.Remove(It.IsAny<Requirement>())).Callback<Requirement>((entity) => requirements.Remove(entity));
    
        context.Setup(x => x.Requirement).Returns(mockDbSet.Object);
    
        var dataAccess = new RequirementsDataAccess(context.Object);
        int idToDelete = 1;
        dataAccess.DeleteRequirement(idToDelete);
    
        context.VerifyGet(x => x.Requirement, Times.Exactly(2));
        //mockDbSet.Verify(x => x.Remove(It.IsAny<Requirement>()), Times.Once());
        context.Verify(x => x.SaveChanges(), Times.Once());
    
        // add this Assert
        Assert.AreEqual(requirement.Count, 2);
        // or
        Assert.IsFalse(requirement.Any(x => x.Id == idToDelete));
    }