Saturday 28 August 2010

Unit Testing Entity Framework 4.0: A-Z in 3 Easy Steps

This article gives you an ultra-fast run down on how to start Unit Testing in Entity Framework 4.0 using C#; applying a suitable Code Generation Item to your Entity Data Model (EDMX) file, and building a Unit Test Project in Visual Studio 2010.

No external test harness is required, it doesn't rely on having a Repository pattern being implemented and you don't need to buy any new products. Just Visual Studio 2010 Professional Edition, Premium Edition or Ultimate Edition out of the box.

Terminology
The EDMX Code Generation Item used in this article generates two ObjectContext classes. When we refer to the Vanilla ObjectContext, this is the same strongly typed ObjectContext that is generated from your EDMX by default. The Mocking ObjectContext we refer to is another context used only for unit testing, which implements the same strongly typed interface, and is also generated from your EDMX file.

More details on the architecture of the artifact generator are provided here.


Step 1: Set Up the Code Generation Item

These steps will change your EDMX Code Generation Item to the ADO.NET Mocking Context Generator. This is what makes your C# code from your Entity Data Model (EDMX) file.
  • Open your EDMX file, right click the background, and from the context menu select Add Code Generation Item.
  • Select Online Templates->Database->ADO.NET Mocking Context Generator:


  • Open your EDMX file, right click the background, and from the context menu select Properties.
  • In the Properties change Code Generation Strategy to None. This will turn off the default Code Generation Item.



Step 2: Ensure "Context Agnostic" Code

Context Agnostic means that your Business Logic code shouldn't be aware of what context class it is using. We need to ensure that any hard dependencies on your generated Vanilla ObjectContext are replaced with references to your new generated ObjectContext Interface instead.

Identify Context "Gnostic" Code

We need to find anywhere in your Business Logic Layer that references the vanilla concrete ObjectContext:

class MovieLogic
{
    private MoviesContext _context;     // Hard dependency on vanilla ObjectContext

    public MovieLogic()
    {
        _context = new MoviesContext();     // Hard dependency on vanilla ObjectContext
    }
}

Option 1: Delegate responsibility to the client

To remove the hard dependency, the simplest option is to ensure all ObjectContext references use the ObjectContext Interface instead, and take on the ObjectContext instance in their constructor:
class MovieLogic
{
    private IMoviesContext _context;    // Interface used instead: context agnostic

    public MovieLogic( IMoviesContext context )
    {
        _context = context;     // Instantiation responsibility is delegated to caller
    }
}
The Business Logic Client can then be re-engineered to instantiate a vanilla ObjectContext object for us when the Business Logic object is instantiated:
MovieLogic logic = new MovieLogic( new MoviesContext() );
Note
Your application will never instantiate a Mocking Context, as this is only used when testing your business logic in your Unit Test Project.



Option 2: Delegate responsibility to a factory

If your Business Logic objects are instantiated all over the place, then it might be easier to contain the instantiation responsibility within a factory, and call the factory when a context is needed from the Business Logic objects themselves:
class MovieLogic
{
    private IMoviesContext _context;

    public MovieLogic()
    {
        _context = MyContextFactory.CreateContext();    
            // Instantiation responsibility is delegated to factory
    }
}
Implementing the Factory
Whilst no implementation of an example factory is given here, the factory just needs to return a new instance of your Vanilla ObjectContext, unless the "testing" flag is raised, when a Mocking ObjectContext will be instantiated instead.



Step 3: Create the Unit Tests

To create the unit tests in Visual Studio, a new test project needs to be created. This project will contain the Unit Test class that runs all the tests, and the code required to set up the mocking context, mock data, and the calls to the business logic.
  • Right click your solution, and select Add -> New Project
  • From the Add New Project Dialog select Visual C# -> Test -> Test Project:


  • Add a Reference in your new test project to your Business Logic Project.
  • Add a Reference to System.Data
  • Add a Reference to System.Data.Entity

Set up the test "database"

This step creates a fresh mocking context before every test is executed. The mocking context is populated with some sample data that the tests can use. Additionally any test can set up its own data as part of its testing.
  • In your new test project, open your Unit Test code file (eg. "UnitTest1.cs")
  • Add a member (eg. _context) to your unit test class, of the type of your ObjectContext Interface. The name of the interface will be shown if you expand the *.Context.tt file in the project containing your EDMX file:

  • private IMoviesContext _context;
  • Add a new function with the TestInitialize attribute. This will cause your function to be executed before every test. Create a mock context and fill it with your mock data. The name of the mocking context class will be shown if you expand the *.Context.tt file in the project containing your EDMX file:

  • [TestInitialize]
    public void TestInitialize() 
    { 
        _context = new MoviesContextMock();
    
        Actor[] actors = new Actor[]
        {
            new Actor() { Id = 1, Name = "Robert De Niro" },
            new Actor() { Id = 2, Name = "Ray Liotta" },
            new Actor() { Id = 3, Name = "Joe Pesci" },
            new Actor() { Id = 4, Name = "Jodie Foster" }
        };
    
        Movie[] movies = new Movie[]
        {
            new Movie() { Id = 1, Title = "Goodfellas",  ReleaseYear = 1990, Rating=5 },
            new Movie() { Id = 2, Title = "Taxi Driver", ReleaseYear = 1976, Rating=5 },
        };
    
        movies[ 0 ].Cast.Add( actors[ 0 ] );
        movies[ 0 ].Cast.Add( actors[ 1 ] );
        movies[ 0 ].Cast.Add( actors[ 2 ] );
        movies[ 1 ].Cast.Add( actors[ 0 ] );
        movies[ 1 ].Cast.Add( actors[ 3 ] );
    
        actors.ToList().ForEach( actor => _context.Actors.AddObject( actor ) );
        movies.ToList().ForEach( movie => _context.Movies.AddObject( movie ) );
    }


Implement Test Methods

Create some functions on your new unit test class, with the attribute TestMethod. These will execute the individual unit tests for your business logic.

Use the Assert.* methods to check the results of calls to your business logic. These calls will be evaluated by the test engine.

[TestMethod]
public void TestGetMovieByTitle()
{
    MovieLogic logic = new MovieLogic( _context );
    Movie movie = logic.GetMovieByTitle( "Goodfellas" );
    Assert.AreEqual( 1, movie.Id );
}

[TestMethod]
public void TestGetMovieByTitleBad()
{
    MovieLogic logic = new MovieLogic( _context );
    Movie movie = logic.GetMovieByTitle( "Arial the Little Mermaid" );
    Assert.AreEqual( null, movie );
}

[TestMethod]
public void TestGetMovieByReleaseYear()
{
    MovieLogic logic = new MovieLogic( _context );
    Movie[] movies = logic.GetMovieByReleaseYear( 1976 );
    Assert.AreEqual( 1, movies.Length );
    Assert.AreEqual( "Taxi Driver", movies[ 0 ].Title );
}



Running the Tests

To run the tests, select Test -> Run -> All Tests In Solution.
The Test Results panel will pop up, and give you the results of your tests:



Frequently Asked Questions

Where's my Vanilla ObjectContext's AddToObjectSetName method gone?

This method has now been deprecated in Entity Framework 4.0. Instead of using AddToObjectSetName, use <My Vanilla ObjectContext>.<ObjectSetName>.AddObject().

How do I unit test my EDMX EntityObject Functions?

Functions map to Stored Procedures in your database. You cannot, therefore, unit test your C# code and expect to call an SP as part of the unit test. You'll have to make sure that the high level logic function that calls the stored procedure is not included in your code C# tests; if you can break the function down into smaller method units within the logic class you may still be able to test the smaller logic units that don't require the SP. For unit testing the SP however, you need to create a Database Unit Test project.

How do I perform a unit test that includes database connectivity?

This, semantically speaking, is not possible. A code Unit Test should be testing units of code. If you need to perform a test across your code into a persistent storage medium, then this is an Integration Test. The Unit Testing phase is performed before the Integration Testing phase.