Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
103 views
in Technique[技术] by (71.8m points)

c# - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 :)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...