1

I have a nicely decoupled app and dependency-injected app that uses Entity Framework 4.1 CodeFirst to expose IQueryable through a repository pattern. It's easy enough to mock the underlying datastore when testing the repository clients, however a certain class of bug is not being caught:

The repository clients are free to layer their own LINQ predicates, joins, etc on top of what the repository returns:

{
     _myRepository.FindAll().Where( x => x.Id == 3 && SomeMethod(x.Name) == "Hello" );
}

This kind of query will succeed in a unit test that mocks _myRepository, because mock returns an in-memory collection of entities and LINQ-to-Objects is happy to call the method "SomeMethod". It will fail against the real data-store because "SomeMethod" does not translate to SQL in LINQ-to-Entities.

I'm trying to figure out a way that I can both mock the dataset and cause the real EF query provider to generate (but not execute) the SQL. Why? Because the tests are supposed to be fast and I don't want them hitting a real database if at all possible. Generating the SQL will flush out translation issues like this.

So far, I have not been able to figure out how to do this, because in my unit tests, I am ultimately not in control of when the query gets materialized. I'm thinking I need to either provide my own version of IQueryable and the various LINQ Queryable extension methods or try and hook in via the provider mechanism (using the sample from a couple of years ago that does Caching/Tracing providers.) Both of these seem like a lot of work. Any ideas on how to achieve this?

jlew
  • 10,491
  • 1
  • 35
  • 58
  • Related: http://stackoverflow.com/questions/1699607/asp-mvc-repository-that-reflects-iqueryable-but-not-linq-to-sql-ddd-how-to-ques – Mark Seemann Apr 22 '11 at 14:11
  • Thanks for the pointer, Mark. We are already using EF 4.1 CodeFirst POCOs, so it's not as leaky as one might think. – jlew Apr 22 '11 at 14:32
  • However, even with POCOs, there is no way to get around the fact that exposing IQueryable means exposing the underlying limitations (in this case, what EF's QueryProvider is able to translate into SQL). As usual, it is a trade-off between purity and productivity. – jlew Apr 22 '11 at 15:13

1 Answers1

5

No there is no way to do that - unless you are going to build your own provider which in turn is not the solution because you must test the real provider not a custom implementation which will not be used in the real code. I discussed it here and here. One more related answer about repository itself.

Simply you can't test database mapping and persistence without doing database mapping and persistence. There is some very strange belief that testing application means writing unit tests. That is wrong definition. Testing application means writing tests and unit tests are just one of many test types but they can't test everything. You need to combine them with other types of tests. The correct approach for this scenario can be integration tests which doesn't have to run every time but can be scheduled on the build server.

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Thanks, we have integration tests. I'm looking to provide developers with as much pre-commit assurance as possible that their queries are well formulated. Integration tests don't do that. – jlew Apr 22 '11 at 14:27
  • @jlew: That is not possible with unit tests because you want to test something which is outside of tested method. That would require create fake provider which will completely simulate EF provider's behavior. That would be a separate project but once you complete it you can sell it. Developer should write integration test and run that test before commit. – Ladislav Mrnka Apr 22 '11 at 14:34
  • I understand what you're saying but not ready to give up yet. It seems to me I should be able to take the Expression tree and submit it to the real EF QueryProvider to translate without executing. My infrastructure, being CodeFirst, is heavily injected so I have pretty good control over what ObjectContext ends up being used by the method under test. I have been trying to wrap the real ObjectContext in a fake one that will use the real one to generate SQL, but return mock data instead of executing that SQL. – jlew Apr 22 '11 at 14:39
  • @jlew: In such case try provider wrappers and try to provide fake `DbCommand`, `DbConnection` etc. But even this approach will not avoid green test and exception at runtime but it can solve the problem with Linq queries. Even that would be pretty challenging because on the wrapper level you get only SQL and based on the SQL you must return raw data to allow EF to materialize entities from them. Also provider wrappers are injected via `EntityConnection` and it doesn't work with code-first. – Ladislav Mrnka Apr 22 '11 at 14:52