Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing: TDD with POCO Objects with navigation properties (relationship fixup)

I've been trying to find a good solution to this but with no luck, so either I'm not searching for the right keywords, or we're doing things wrong from the start so the problem shouldn't really exist.

Update for clarification: I would like this to work as a unit test rather than as an integration test, so I don't want this to hit the database, but I want to mock the associations made when EF persists changes in my unit test.

Original question:

Say you are testing a service method like so:

[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);
}

Now lets say that this test fails because AssignAuthorToBook actually works using the code book.Author = author; so it is not assigning the AuthorId, it is assigning the entity. When this is persisted using the Entity Framework SaveChanges() method on the context it will associate the entities and the IDs will correlate. However, in my example above the logic of Entity Framework would not have been applied. What I am saying is the code will work once SaveChanges() has been called, but the unit test will fail.

In this simple example, you'd probably know straight away why your test had failed as you had just written the test immediately before the code and could easily fix it. However, for more complicated operations and for operations where future changes that may change the way entities are associated, which will break tests but may not break functionality, how is unit testing best approached?

My thoughts are:

  • The service layer should be ignorant of the persistence layer - should we mock the Data Context in the unit tests to mock the manner that it works? Is there an easy way to do this that will automatically tie up the associations (i.e. assign to correct entity if Id is used or assign the correct Id if the entity is used)?
  • Or should the tests be structured in a slightly different manner?

The tests that exist in the current project I have inherited work as in my example above, but it niggles with me that there is something wrong with the approach and that I haven't managed to find a simple solution to a possibly common problem. I believe the Data Context should be mocked, but this seems like a lot of code will need to be added to the mock to dynamically create the associations - surely this has already been solved?

Update: These are the closest answers I've found so far, but they're not quite what I'm after. I don't want to test EF as such, I just wondered what was best practise for testing service methods that access repositories (either directly or via navigation properties through other repositories sharing the same context).

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

Conclusion so far: That this is not possible using unit testing, and only possible using integration testing with a real DB. You can get close and probably code something to dynamically associate the navigation properties, but your mock data context will never quite replicate the real context. I would be happy if any solution enabled me to automatically associate the navigation properties which would allow my unit tests to be better, if not perfect (the nature of a successful unit test doesn't by any means guarantee functionality anyway) The ADO.NET Mocking Context Generator comes close, but it appears that I'll have to have a mock version of every entity which will not work for me in case functioanlity is added to them using partial classes in my implementation.

like image 782
SilverlightFox Avatar asked Aug 14 '13 15:08

SilverlightFox


3 Answers

That will never work the way you have it. The data context is wrapped up behind the scenes in your service layer, and the association of the BookID is never going to get updated on your local variable.

When I have done TDD on something using EF, I generally wrap up all my EF logic into some kind of DAO and have CRUD methods for the entities.

Then you could do something like this:

[Test]
public void AssignAuthorToBook_NewBookNewAuthor_SuccessfullyAssigned()
{
  IBookService service = new BookService();

  var book = new Book();
  var bookID = 122;
  book.ID = bookId;
  var Author = new Author() { Id = 123 };

  service.AssignAuthorToBook(book, author);

//ask the service for the book, which uses EF to get the book and populate the navigational properties, etc...
  book = service.GetBook(bookID)

  Assert.AreEqual(book.AuthorId, 123);
}
like image 29
Joe Brunscheon Avatar answered Sep 20 '22 00:09

Joe Brunscheon


I'd argue that you are expecting a result from your test that implies the use of several dependencies, arguably not qualifying it as a unit test, especially because of an implied dependency on EF.

The idea here is that if you acknowledge that your BookService has a dependency on EF you should use a mock to assert it interacts correctly with it, unfortunately EF doesn't seem to like to be mocked, so we can always put it under a repository, here's an example of how that test could be written using 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));
}

The id being set is something that you would use an integration test for, but I'd argue that you shouldn't worry about the EF failing to set the Id, I say this for the same reason you should not worry about testing if the .net framework does what it's supposed to do.

I've written about interaction testing in the past (which I think is the right way to go in this scenario, you are testing the interaction between the BookService and the Repository), hope it helps: http://blinkingcaret.wordpress.com/2012/11/20/interaction-testing-fakes-mocks-and-stubs/

like image 172
Rui Avatar answered Sep 20 '22 00:09

Rui


I was having the same problem as you and came across your post. What I found after was a in memory database called Effort. Take a look at Effort

The following test works correctly

        EntityConnection conn = Effort.EntityConnectionFactory.CreateTransient("name=MyEntities");

        MyEntities ctx = new MyEntities(conn);

        JobStatus js = new JobStatus();
        js.JobStatusId = 1;
        js.Description= "New";

        ctx.JobStatuses.Add(js);

        Job j = new Job();
        j.JobId = 1;
        j.JobStatus = js;
        ctx.Jobs.Add(j);

        ctx.SaveChanges();

        Assert.AreEqual(j.JobStatusId, 1);

Where MyEntities is a DbContext created with a Effort connection string.

You still need to create you in memory objects, but, save changes on the context sets the objects associations as a database does.

like image 31
Nick Crisp Avatar answered Sep 18 '22 00:09

Nick Crisp