Unit test Entity Framework 4.3+ and confirm you have valid LINQ-to-Entities code without any need for a database connection.
I agreed with Ladislav Mrnka
* for a long time that you should _only_ use integration tests
with a live DB for LINQ-to-Entities queries because of the problems with distinction between LINQ-to-Objects (which will be used for any kind of mock DbContext) and LINQ-to-Entities.
Then I realized the problem is, we're mocking the wrong thing! We want a real DbContext
with all the EF stuff that goes with it to test our logic, we just don't want that context to depend on a real database. So what we really need to mock is the DbConnection
that the context uses internally to talk to a database.
Because we're using a real DbContext
, we cannot mock the internals directly, however, EF 4.3
supports a DefaultConnectionFactory
which creates the DbConnection
"The new configuration section allows you to specify a default connection factory that Code First should use to locate a database to use for a context. The default connection factory is only used when no connection string has been added to the configuration file for a context."
If we inject a factory which creates a fake connection before building our context, we can create a unit test of EF code which actually uses LINQ-to-Entities and no database! A couple of notes:
- EF relies on the DbProviderServices and DbProviderManifest to do a lot of the translation between EDM types and SQL types, among other important things. My code reflects onto the SqlProviderServices and SqlProviderManifest (declared Friend inside of SqlClient in System.Data.Entity.dll) to do this work. To make the code work for non-SQL db's, you'll have to do similar reflection for their client.
- The default IDatabaseInitializer is CreateDatabaseIfNotExists. That requires a lot of extra mocking inside your DbConnection to "pretend" to create a database. To avoid this, I created a DoNotCreateDatabaseInitializer which does nothing in the InitializeDatabase method and reduced the mocking my fake conneciton needs.
- NOTE: Lots of well known .NET / EF developers subscribe to the integration testing only mentality, Ladislav just happens to be the most vocal AFAIK.
To use the project, just reference FakeDbConnection in your project and call FakeDbConnection.FakeConnectionConnectionFactory.RegisterFactory(Of YourContextType)()
before you create context object to test. You cannot verify data because we're not mocking the DB here, but you can verify the code makes valid SQL. Here's a full sample (in VB w/ NUnit):
Public Class YourTestClass
Public Sub Your_Test()
Dim context As New YourContextType("sql server year")
Dim actualData As IEnumerable(Of YourModelType) = context.YourModels.Where(Function(YourModel) YourModel.ID = 1)
'Assert we have a valid EF query
Assert.IsTrue(TypeOf actualData Is Entity.Infrastructure.DbQuery(Of YourModelType))
'Assert it can compile to valid SQL