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