Walkthrough: Test-Driven Development with the Entity Framework 4.0

This walkthrough will demonstrate how to use the new Plain Old CLR Object (POCO) support in Entity Framework 4.0 to develop an application using Test-Driven Development (TDD).

In the first release of EF, entity classes had to inherit from EntityObject or implement one of the EF interfaces, which meant your classes had to have direct dependencies on the EF. As a result, writing unit tests that verified the logic for just your domain objects was challenging. The tests would have to interact with the database directly, which violates the principle of persistence ignorance, and results in the tests running orders of magnitude slower.

With Entity Framework 4.0, you can use the Repository and Unit of Work pattern to create domain entities that are completely EF-agnostic. With the hard dependency on the EF removed, it’s much easier to create fakes and mocks of these objects when writing unit tests. These tests no longer need to hit the database at all, offering a clean separation of concerns, reducing complexity, and making unit tests run significantly faster.

For more background information on TDD improvements in EF 4.0, check out Diego’s excellent post here.

Requirements

1. This walkthrough requires Visual Studio 2010 Beta 2.

2. Download and extract the initial solution attached to this post.

3. If you want to run the application against an actual database, you can generate it from the model. However, the whole point of the walkthrough is to allow not working against an actual database, but rather to use in-memory fake objects instead.

Walkthrough

In this walkthrough, we’re going to use the repository pattern to encapsulate our database interaction, and create fake objects that allow us to test our domain logic without having to touch the database.

Part 1 – Getting Familiar with the Project

1. Open the attached solution and double-click on Blogging.edmx. We’ll be using the same model we used in the CodeOnly walkthrough:

Untitled

2. In the designer click on the white background surface and press F4 to bring up the properties window. You’ll see that the “Code Generation Strategy” property has already been set to “None”. This tells the designer not to generate code for each entity. Instead we’ll be writing our own POCO entity classes. The Entity Framework’s POCO support is the critical piece needed to be able to write domain objects that can be tested easily.

Untitled

 

3. Open each of the Entity classes (i.e. Blog.cs, Person.cs etc). Notice how they’re just regular POCOs; they don’t inherit from EntityObject or any other class/interface in the Entity Framework.

4. Open the file BloggingEntities.cs. This class inherits from ObjectContext and exposes an ObjectSet<TEntity> for each entity in the model:

 public partial class BloggingEntities : ObjectContext
    {
    
        public virtual ObjectSet<Blog> Blogs { ... }
       
        public virtual ObjectSet<Comment> Comments { ... }
          
        public virtual ObjectSet<Person> People { ... }
       
        public virtual ObjectSet<Post> Posts { ... }
       
        public virtual ObjectSet<User> Users { ... }
        
        ...
    }

Unlike our POCO objects, this class does have direct dependencies on types defined in the Entity Framework. If we write our tests to use this class then our tests would be hitting the database. Since we want to avoid this, we’ll spend the rest of the walkthrough exploring how to make a fake implementation of this class that our testbed can consume.

Part 2 – Setting up the Tests

5. In Visual Studio, click Test -> “New Test…”, select “Basic Unit Test”, and call the file “CommentTests.cs”. Click OK, then type “BlogTests” when prompted to name the project and click “Create”. Visual Studio will create a new Test Project where we can write our unit tests.

Untitled

6. Add a reference from the Test project to the BlogManager class library.

7. Save the solution.

Part 3 – Implementing the Repository Pattern

8. Add a new interface to BlogManager and call it IBloggingEntities.cs. Paste in the following code:

 using System;
using System.Data.Objects;

namespace BlogManager
{
    public interface IBloggingEntities : IDisposable
    {
        IObjectSet<Blog> Blogs { get; }
        IObjectSet<Comment> Comments { get; }
        IObjectSet<Person> People { get; }
        IObjectSet<Post> Posts { get; }
        IObjectSet<User> Users { get; }

        int SaveChanges();
    }
}

This interface represents all the important operations on our ObjectContext (which is in BloggingEntities.cs). Now our tests can work against the interface rather than against the concrete object type, which will make it easier for us to substitute a fake ObjectContext. Substituting a fake means we can remove the need to interact with the database, which will speed up the test and eliminate the hassle of needing to reset the database to its initial state after each test run.

Similarly, each of the properties in the interface return IObjectSet instead of ObjectSet. Since we’re returning the interface type, we’ll also be able to substitute our own FakeObjectSet implementations for testing.

9. Modify the BloggingEntities class so that it implements the IBloggingEntities interface.

Untitled

When you select the option to implement the interface from the smart tag, the IDE will generate the necessary properties at the bottom of the class. Replace the NotImplementedExceptions with the following code:

 IObjectSet<Blog> IBloggingEntities.Blogs
{
     get { return this.Blogs; }
}

IObjectSet<Comment> IBloggingEntities.Comments
{
     get { return this.Comments; }
}

IObjectSet<Person> IBloggingEntities.People
{
     get { return this.People; }
}

IObjectSet<Post> IBloggingEntities.Posts
{
     get { return this.Posts; }
}

IObjectSet<User> IBloggingEntities.Users
{
     get { return this.Users; }
}

10. Add a new class to BlogManager called BlogRepository.cs. Paste in the following code:

 using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;

namespace BlogManager
{
    public class BlogRepository
    {
        private IBloggingEntities _context;

        public BlogRepository(IBloggingEntities context)
        {
            if (context == null)
                throw new ArgumentNullException("context was null");
            _context = context;
        }

    }
}

The repository will act as a thin mapper between our domain objects (Blog, Post, User etc) and our Data Access Layer (our BloggingEntities class which inherits from ObjectContext). The key is that the repository itself is persistence ignorant, it’s just going to work against the IBloggingEntities interface, and doesn’t care what the actual backing store is.

As we go on and write our tests, we’ll let the tests dictate which methods we actually need to create in our repository.

Part 4 – Writing the Tests

11. In order to test out the Comment class we’re going to need some sample data since it has relationships to other entities. Replace the code in the CommentTests class with the following:

 using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BlogManager;

namespace BlogTests
{
    [TestClass]
    public class CommentTests
    {
        private IBloggingEntities _context;

        [TestInitialize]
        public void TestSetup()
        {
            Person p = new Person()
            {
                fn = "Jonathan",
                ln = "Aneja",
                email = "jonaneja@microsoft.com",
                User = new User() { Password = "password123" }
            };


            Blog b = new Blog()
            {
                Name = "Entity Framework Team Blog",
                Url = "https://blogs.msdn.com/adonet",
                User = p.User
            };

            Post post = new Post()
            {
                ID=1,
                Blog = b,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Title = "Walkthrough: Test-Driven Development in Entity Framework 4.0",
                User = p.User,
                User1 = p.User,
                PermaUrl = b.Url + "/walkthrough-TDD-in-Entity-Framework-4.0.aspx",
                Body = "This walkthrough will demonstrate how to..."
            };

        }
    }
}

12. Before the end of the TestSetup method type in the following code, then press Ctrl+Dot to bring up the following smart tag:

Untitled

Press Enter and the IDE will automatically create the FakeBloggingEntities class. (Note there’ll still be a compile error since the auto-generated class doesn’t implement IBloggingEntities yet. We’ll add that in a few steps).

13. Type the following code at the bottom of TestSetup():

Untitled

For each method use the Ctrl+Dot plus Enter trick to generate the actual methods. What’s great about this feature is it allows you to keep your flow without having to switch between windows constantly.

14. Now we’re ready to write our first test. The functionality we want to verify is that our application will reject any comment that’s been double-posted to the same entry. Paste in the following method into the CommentTests class:

 [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void AttemptedDoublePostedComment()
        {
            var repository = new BlogRepository(_context);

            Comment c = new Comment()
            {
                ID = 45,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "EF4 is cool!",
                Title = "comment #1",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };

            Comment c2 = new Comment()
            {
                ID = 46,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "EF4 is cool!",
                Title = "comment #1",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };

            repository.AddComment(c);
            repository.AddComment(c2);

            repository.SaveChanges();
        }

Again, use the Ctrl+Dot plus Enter trick to generate method stubs for GetPersonByEmail, GetPostByID, and AddComment.

Notice that the Body, Title, and Person properties are all the same for both comments (we’ll implement logic in the Comment class later to throw an exception when attempting to double-post a comment).

Also notice the ExpectedException attribute at the top of the method. This tells the test infrastructure to expect an InvalidOperationException for this method, and fail if such an exception is not thrown.

15. Add a reference to System.Data.Entity.dll (this is required so that we can use the IObjectSet<T> interface in our fake classes).

16. In FakeBloggingEntities, add the using statements below, implement the IBloggingEntities interface, and press Ctrl + Dot plus Enter to automatically generate the methods.

Untitled

Do the same thing for the IDisposable interface as well.

17. Click Test -> Run -> All Tests in Solution. We want to ensure the test fails since we haven’t actually implemented any logic yet.

Untitled

18. Now we’ll start writing the code to make our test pass. Remove all the NotImplementedExceptions from FakeBloggingEntities and for each IObjectSet property implement the following pattern (i.e. do it for User/Post/Person/Comment as well) :

 public IObjectSet<Blog> Blogs
        {
            get
            {
                return _blogs ?? (_blogs = new FakeObjectSet<Blog>());
            }
            set
            {
                _blogs = value as FakeObjectSet<Blog>;
            }
        }
        private FakeObjectSet<Blog> _blogs;

Also implement SaveChanges and Dispose:

public int SaveChanges() 
       {
            foreach (var comment in Comments)
            {
                ((IValidate)comment).Validate(ChangeAction.Insert);
            }
            return 1;
        }
        public void Dispose() {}

19. Create the following FakeObjectSet<T> class. This will be a fake implementation of an IObjectSet that is backed by an in-memory HashSet<T> (rather than by a database table). IObjectSet derives from IQueryable and so we need to implement its members as well:

 using System;
using System.Linq;
using System.Collections.Generic;
using System.Data.Objects;

namespace BlogManager
{
    public class FakeObjectSet<T> : IObjectSet<T> where T : class
    {
        HashSet<T> _data;
        IQueryable _query;

        public FakeObjectSet() : this(new List<T>()) { }

        public FakeObjectSet(IEnumerable<T> initialData)
        {
            _data = new HashSet<T>(initialData);
            _query = _data.AsQueryable();
        }

        public void Add(T item)
        {
            _data.Add(item);
        }

        public void AddObject(T item)
        {
            _data.Add(item);
        }

        public void Remove(T item)
        {
            _data.Remove(item);
        }

        public void DeleteObject(T item)
        {
            _data.Remove(item);
        }

        public void Attach(T item)
        {
            _data.Add(item);
        }

        public void Detach(T item)
        {
            _data.Remove(item);
        }

        Type IQueryable.ElementType
        {
            get { return _query.ElementType; }
        }

        System.Linq.Expressions.Expression IQueryable.Expression
        {
            get { return _query.Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return _query.Provider; }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _data.GetEnumerator();
        }
    }
}

20. Remove all the NotImplementedExceptions from BlogRepository and implement the following logic:

 public void AddPerson(Person p)
        {
            _context.People.AddObject(p);
        }

        public void AddBlog(Blog b)
        {
            _context.Blogs.AddObject(b);
        }

        public void AddPost(Post post)
        {
            _context.Posts.AddObject(post);
        }

        public void SaveChanges()
        {
            _context.SaveChanges();
        }

        public Person GetPersonByEmail(string email)
        {
            return _context.People.First(p => p.email == email);
        }

        public Post GetPostByID(int postID)
        {
            return _context.Posts.First(p => p.ID == postID);
        }

        public void AddComment(Comment c)
        {
            _context.Comments.AddObject(c);
        }

21. At this point run the tests again. Now that we’ve written all the infrastructure, the entire test can actually run, and we get the following result:

Untitled

22. Replace the existing IValidate.Validate method in the Comment class and add a using directive for System.Linq.

 void IValidate.Validate(ChangeAction action) 
        {
            if (action == ChangeAction.Insert)
            {
                //prevent double-posting of Comments
                if (this.Post.Comments.Count(c => c.Body == this.Body && 
                                             c.Person.User == this.Person.User) > 1)
                    throw new InvalidOperationException(
                "A comment with this exact text has already been posted to this entry");
            }
        }

23. Now run the test again, and this time it passes!

Untitled

24. We’re going to add one more feature – we want to validate that any comment posted to the blog actually contains some text. Now that we have all of our infrastructure wired up, adding the feature and its corresponding test can be done very quickly.

First we’ll write the test that verifies this functionality and add it to the CommentTests class:

         [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void AttemptBlankComment()
        {
            var repository = new BlogRepository(_context);

            Comment c = new Comment()
            {
                ID = 123,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "",
                Title = "some thoughts",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };

            repository.AddComment(c);
            repository.SaveChanges();
        }

Go ahead and run the test and verify that it fails:

Untitled

25. In the Comment class’ Validate method add the following code:

 if (String.IsNullOrEmpty(this.Body))
           throw new InvalidOperationException(
"Comment must contain some text in its Body");

26. Run both tests and this time they should both pass:

Untitled

Summary

In this walkthrough we explored how Entity Framework 4.0’s POCO support makes it easier to create a testable, persistence-ignorant architecture. We saw how to implement the Repository pattern, and take advantage of new IDE features that make TDD easier. Finally, we saw how IObjectSet<T> makes it easy for us to create fake implementations that can be tested without having to touch the database.

We’d love to hear your feedback, so let us know what you think!

Jonathan Aneja

Program Manager, Entity Framework Team

 

TDDWalkthrough.zip