对dbContext进行模拟唯一约束检查

时间:2017-07-30 06:31:35

标签: entity-framework unit-testing moq

我有关于dbContext的目的而嘲笑unit testing的问题。

在我的下面代码中,我使用Moq library模拟dbContext和DBSet条目,然后从Create触发service方法,最后验证SaveChanges至少成功命中一次。

public void Create_Test_Item()
{

    // creates a DbSet<TestItem>
    var mockSet = new Mock<DbSet<TestItem>>();
    mockSet.Setup(x => x.Add(It.IsAny<TestItem>())).Returns((TestItem testChildItem) => testChildItem);
    // uses Moq to create a TestContext.
    var mockContext = new Mock<TestContext>() { CallBase = true };

    //wires it up to be returned from the context’s TestItem property.
    mockContext.Setup(c => c.Set<TestItem>()).Returns(mockSet.Object);

    mockContext.Setup(c => c.SaveChanges()).Returns(1);

    //context is used to create a new TestsvcInstance<TestItem> which is then used to create a new TestItem
    var svcInstance = new TestsvcInstance<TestItem>(mockContext.Object);

    var TestItem = new TestItem
    {
        Name = "B01",
        Code = "001",
        ModuleId = new Guid("1F8B2910-C5D4-E611-80D0-000D3A80FCC4")
    };

    svcInstance.Create(TestItem);

    // Finally, the test verifies that the svcInstance added a new TestItem and called SaveChanges on the context.
    mockSet.Verify(m => m.Add(It.IsAny<TestItem>()), Times.Once());
    mockContext.Verify(m => m.SaveChanges(), Times.Once());

}
  

我正在尝试进一步扩展它以添加唯一检查。假如我试试   使用TestItem创建Code / Name已保存在基础中的mocking implementation should raise an error   上下文,我的DROP TABLE tmdb_movies; CREATE TABLE tmdb_movies ( tmdb_id INTEGER NOT NULL PRIMARY KEY, movie_title TEXT NOT NULL, popularity INTEGER NOT NULL ); INSERT INTO tmdb_movies (tmdb_id, movie_title, popularity) VALUES (1, 'Logan', '88.4'), (2, 'Iron Man', '74.3'), (3, 'SuperMan', '102.56'); DROP TABLE genres; CREATE TABLE genres ( tmdb_id INTEGER NOT NULL, genres_name TEXT NOT NULL ); INSERT INTO genres (tmdb_id, genres_name) VALUES (1, 'Crime'), (1, 'Comedy'), (1, 'Drama'), (2, 'Action'), (2, 'Horror'), (2, 'Documentary'), (3, 'Music'); SELECT distinct genres.tmdb_id FROM genres JOIN tmdb_movies USING (tmdb_id) ORDER BY tmdb_movies.popularity DESC

我如何通过同样的嘲弄理念实现这一目标?

1 个答案:

答案 0 :(得分:1)

您的测试目标应该是测试您的业务逻辑,而不是EF。如果要确保使用唯一约束设置数据库,请使用针对实际数据库的集成测试。

就单元测试而言,可能会设置业务逻辑以避免重复的唯一值。这意味着,如果您的代码将考虑重复值,您的服务/ DbContext(如果您必须深入)可以被模拟以期望验证调用断言所需的值不是重复的。

因此,假设我有一个例程来验证新用户的用户名是唯一的。该代码转到用户DBSet,它会进行一些过滤,或者您要确定用户名是否唯一。即DbContext.Users.Any(u=>u.UserName = userName);

如果我在模拟DBContext,那么我会在这种情况下模拟Users DBSet,以返回一个List<User>,其中包含一个名称与我正在测试的名称相匹配的用户。我的测试代码应该收到该用户,验证失败并且我的测试应该通过。我也断言我的DBContext.SaveChanges在那个场景中没有被调用。所以我告诉模拟“嘿,给我一些我应该知道有一个重复的用户名,并确保在这种情况下我不会调用SaveChanges。”

要测试代码如何处理重复的ID约束违规,可以将模拟的DbContext设置为抛出预期的异常,以便您可以断言代码处理该异常的方式。您不需要经历设置模拟以“检测”重复约束的麻烦,只需告诉它“我将给您一个,所以这是您需要做的。”

所以上面的例子说我们想要测试一个竞争条件(其他人刚刚插入该用户,或者其他一些开发人员用一些额外的过滤器破坏了我的简单.Any()检查,导致重复的用户不在在这种情况下,我会模拟Users DBSet返回一个空列表,例如,然后模拟SaveChanges调用以抛出异常。(与你接收插入重复记录的类型相同)从那里你可以断言您正在测试的代码的行为。(它是否调用日志服务,返回不同的结果?等等)

Moq看起来像:

mockDbContext.Setup(x=>x.SaveChanges()).Throws<SomeDbException>("Suitable message.");

虽然我会说虽然你可以对这种行为进行单元测试,但单元测试应该更多地关注你的业务逻辑。 (即它是否按预期进行了验证?)可以设置针对真实数据库或内存数据库的集成测试来处理边缘情况。集成测试和单元测试之间的区别在于集成测试每天或每天运行几次,而使用模拟的单元测试设计为在您开发业务逻辑时重复运行,此时存在副作用的风险。形成围绕代码/逻辑的安全网,可能会不断变化。约束是相对静态的行为。