更新澄清:我希望这可以作为单元测试而不是集成测试,所以我不希望这个命中数据库,但是我想模拟EF在单元测试中持续更改时所做的关联.
原始问题:
假设您正在测试这样的服务方法:
[Test] public void AssignAuthorToBook_NewBookNewAuthor_SuccessfullyAssigned() { IBookService service = new BookService(); var book = new Book(); var Author = new Author() { Id = 123 }; service.AssignAuthorToBook(book,author); Assert.AreEqual(book.AuthorId,123); }
现在让我们说这个测试失败了,因为AssignAuthorToBook实际上使用了代码簿.Author = author;所以它没有分配AuthorId,它正在分配实体.当在上下文中使用Entity Framework SaveChanges()方法持久保存时,它将关联实体,ID将相关联.但是,在上面的示例中,不会应用实体框架的逻辑.我所说的是,一旦调用了SaveChanges(),代码将起作用,但单元测试将失败.
在这个简单的例子中,您可能会立即知道为什么测试失败,因为您刚刚在代码之前编写了测试并且可以轻松修复它.但是,对于更复杂的操作以及可能改变实体关联方式的未来更改的操作,这将破坏测试但可能不会破坏功能,如何最好地接近单元测试?
我的想法是:
>服务层应该不知道持久层 – 我们应该在单元测试中模拟数据上下文来模拟它的工作方式吗?是否有一种简单的方法可以自动绑定关联(即,如果使用Id则分配给正确的实体,或者如果使用实体则分配正确的ID)?
>或者测试的结构应该略有不同?
我在上面的例子中继承了当前项目中存在的测试,但是我发现这个方法存在问题并且我没有设法找到可能常见问题的简单解决方案.我相信应该嘲笑数据上下文,但这似乎需要将大量代码添加到模拟中以动态创建关联 – 当然这已经解决了?
更新:这是我到目前为止找到的最接近的答案,但它们并不是我所追求的.我不想这样测试EF,我只是想知道什么是测试访问存储库的服务方法的最佳实践(直接或通过导航属性通过共享相同上下文的其他存储库).
How Do I Mock Entity Framework’s Navigational Property Intelligence?
Mocked datacontext and foreign keys/navigation properties
Fake DbContext of Entity Framework 4.1 to Test
Navigation properties not set when using ADO.NET Mocking Context Generator
到目前为止的结论:使用单元测试是不可能的,并且只能使用与真实数据库的集成测试.您可以接近并可能编写一些动态关联导航属性的代码,但您的模拟数据上下文永远不会完全复制真实的上下文.如果任何解决方案能够让我自动关联导航属性,我会很高兴,这样可以让我的单元测试变得更好,如果不是完美的(成功的单元测试的性质无论如何都不能保证功能)ADO.NET Mocking Context Generator关闭,但似乎我必须有一个模拟版本的每个实体,如果在我的实现中使用部分类添加功能,将无法为我工作.
解决方法
这里的想法是,如果您承认您的BookService依赖于EF,您应该使用模拟来断言它与它正确交互,不幸的是EF似乎不喜欢被模拟,所以我们总是把它放在一个存储库下,这是一个如何使用Moq编写测试的示例:
[Test] public void AssignAuthorToBook_NewBookNewAuthor_CreatesNewBookAndAuthorAndAssociatesThem() { var bookRepositoryMock = new Mock<IBookRepository>(MockBehavior.Loose); IBookService service = new BookService(bookRepositoryMock.Object); var book = new Book() {Id = 0} var author = new Author() {Id = 0}; service.AssignAuthorToBook(book,author); bookRepositoryMock.Verify(repo => repo.AddNewBook(book)); bookRepositoryMock.Verify(repo => repo.AddNewAuthor(author)); bookRepositoryMock.Verfify(repo => repo.AssignAuthorToBook(book,author)); }
设置的id是你要使用集成测试的东西,但是我认为你不应该担心EF没有设置Id,我说这是因为你不应该担心测试是否.net框架做了它应该做的事情.
我以前写过关于交互测试的文章(我认为这是正确的方法,你正在测试BookService和Repository之间的交互),希望它有所帮助:http://blinkingcaret.wordpress.com/2012/11/20/interaction-testing-fakes-mocks-and-stubs/