Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing with EF4 "Code First" and Repository

I am attempting to get a handle on Unit testing a very simple ASP.NET MVC test app I've built using the Code First approach in the latest EF4 CTP. I'm not very experience with Unit testing / mocking etc.

This is my Repository class:

public class WeightTrackerRepository {     public WeightTrackerRepository()     {         _context = new WeightTrackerContext();     }      public WeightTrackerRepository(IWeightTrackerContext context)     {         _context = context;     }      IWeightTrackerContext _context;      public List<WeightEntry> GetAllWeightEntries()     {         return _context.WeightEntries.ToList();     }      public WeightEntry AddWeightEntry(WeightEntry entry)     {         _context.WeightEntries.Add(entry);         _context.SaveChanges();         return entry;     } } 

This is IWeightTrackerContext

public interface IWeightTrackerContext {     DbSet<WeightEntry> WeightEntries { get; set; }     int SaveChanges(); } 

...and its implementation, WeightTrackerContext

public class WeightTrackerContext : DbContext, IWeightTrackerContext {     public DbSet<WeightEntry> WeightEntries { get; set; } } 

In my test, I have the following:

[TestMethod] public void Get_All_Weight_Entries_Returns_All_Weight_Entries() {     // Arrange     WeightTrackerRepository repos = new WeightTrackerRepository(new MockWeightTrackerContext());      // Act     List<WeightEntry> entries = repos.GetAllWeightEntries();      // Assert     Assert.AreEqual(5, entries.Count); } 

And my MockWeightTrackerContext:

class MockWeightTrackerContext : IWeightTrackerContext {     public MockWeightTrackerContext()     {         WeightEntries = new DbSet<WeightEntry>();         WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("01/06/2010"), Id = 1, WeightInGrams = 11200 });         WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("08/06/2010"), Id = 2, WeightInGrams = 11150 });         WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("15/06/2010"), Id = 3, WeightInGrams = 11120 });         WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("22/06/2010"), Id = 4, WeightInGrams = 11100 });         WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("29/06/2010"), Id = 5, WeightInGrams = 11080 });     }      public DbSet<WeightEntry> WeightEntries { get;set; }      public int SaveChanges()     {         throw new NotImplementedException();     } } 

My problem occurs when I'm trying to build up some test data as I can't create a DbSet<> as it has no constructor. I get the feeling I'm barking up the wrong tree with my whole approach trying to mock my context. Any advice would be most welcome to this complete unit testing newbie.

like image 679
DavidGouge Avatar asked Aug 11 '10 21:08

DavidGouge


People also ask

What are the three steps in a unit test?

The idea is to develop a unit test by following these 3 simple steps: Arrange – setup the testing objects and prepare the prerequisites for your test. Act – perform the actual work of the test. Assert – verify the result.

Is Entity Framework a repository pattern?

No, the repository/unit-of-work pattern (shortened to Rep/UoW) isn't useful with EF Core. EF Core already implements a Rep/UoW pattern, so layering another Rep/UoW pattern on top of EF Core isn't helpful.


1 Answers

You create a DbSet through the Factory method Set() on the Context but you don't want any dependency on EF in your unit test. Therefore, what you need to look at doing is implementing a stub of DbSet using the IDbSet interface or a Stub using one of the Mocking frameworks such as Moq or RhinoMock. Assuming you wrote your own Stub you'd just add the WeightEntry objects to an internal hashset.

You may have more luck learning about unit testing EF if you search for ObjectSet and IObjectSet. These are the counterparts to DbSet prior to the code first CTP release and have a lot more written about them from a unit testing perspective.

Here's an excellent article on MSDN that discusses testability of EF code. It uses IObjectSet but I think it's still relevant.

As a response to David's comment I'm adding this addendum below as it wouldn't fit in the -comments. Not sure if this is the best practice for long comment responses?

You should change the IWeightTrackerContext interface to return an IDbSet from the WeightEntries property rather than a DbSet concrete type. You can then create a MockContext either with a mocking framework (recommended) or your own custom stub. This would return a StubDbSet from the WeightEntries property.

Now you will also have code (i.e Custom Repositories) that depend on the IWeightTrackerContext which in production you would pass in your Entity Framework WeightTrackerContext that would implement IWeightTrackerContext. This tends to be done through constructor injection using an IoC framework such as Unity. For testing the repository code that depends on EF you would pass in your MockContext implementation so the code under test thinks it's talking to the "real" EF and database and behaves (hopefully) as expected. As you have removed the dependancy on the changeable external db system and EF you can then reliably verify repository calls in your unit tests.

A big part of the mocking frameworks is providing the ability to verify calls on Mock objects to test behaviour. In your example above your test is actually only testing the DbSet Add functionality which shouldn't be your concern as MS will have unit tests for that. What you'd want to know is that the call to the Add on DbSet was made from within your own repository code if appropriate and that is where the Mock frameworks come in.

Sorry I know this is a lot to digest but if you have a read through that article a lot will become clearer as Scott Allen is a lot better at explaining this stuff than I am :)

like image 138
Darren Lewis Avatar answered Oct 03 '22 17:10

Darren Lewis