The business layer of an application should generally access the database through an abstraction such as a service, command or query class. This has two benefits:
- It expresses our intent whilst hiding the details of the database schema and implementation.
- We can mock the service / command / query in order to write unit tests for the business logic: "If the query returns x, then y should happen."
So far, so good. But how should we test-drive the development of the service class?
It is tempting to make the service depend on an abstract repository or unit of work so that it too is isolated from the database. However, this strategy can ultimately result in unit tests which simply verify that abstraction A calls the right methods on abstraction B, which calls the right methods on abstraction C and so on. At this point we are creating classes that don't do any actual work themselves, just so that we can create tests for the sake of having tests.
Bear in mind that:
- Entity Framework provides an abstraction that hides the underlying database provider. Our code does not need to know whether it is talking to SQL Server, Oracle, SQLite or some other obscure database system.
- The Entity Framework database context is effectively a unit-of-work implementation. Any changes that we make are applied atomically when we call
SaveChanges()
. - If Entity Framework is hidden behind some sort of generic
IRepository<T>
, then it becomes more difficult to use it efficiently. If the repository does not know what its output is being used for, then it cannot tell which foreign key references should be eagerly loaded and which ones should not. - If our service needs to perform a complicated query, then unit tests cannot tell us whether the query is correct. The only way to be sure is to run the query against a database with known contents.
The simplest way to implement our service, then, is to allow it to use the Entity Framework context directly and accept that it can only be tested at the integration level. This is not necessarily a bad thing if we can find an efficient and repeatable way to run the integration tests.
Effort is a library that connects Entity Framework to an in-memory database. This makes it easy (at least in theory) to write integration tests for your data access layer which always start from a known database state, and don't require a real database server.
You probably want the Effort.EF6 package from NuGet.
- Effort documentation
- Using an In Memory Database as a Test Double with Entity Framework
- Save time mocking – use your real Entity Framework DbContext in unit tests
Respawn could be helpful here. It provides functions for resetting a database back to a known state by deleting data from tables.
See also the blog post "Powerful Integration Testing" at Los Techies.
Effort has moved to https://entityframework-effort.net/