dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.81k stars 3.2k forks source link

InMemory provider support for mocking raw queries and other relational commands #21068

Open APIWT opened 4 years ago

APIWT commented 4 years ago

I would like to be able to mock relational commands and assert on these calls when using the InMemory database provider. At the same time, I would like the InMemory provider to continue to support basic DbContext and DbSet operations that it currently supports. I think this is a really important piece of writing good integration tests for ASP.NET Core Controllers and Azure Functions.

I think that SQLite works well most of the time, but the problem is that there are times where you form custom queries specific to your database provider. For example, I am use SQL Server and the syntax of queries is quite a bit different ie: SELECT * FROM [Users] vs SELECT * FROM Users (this is an oversimplification, I realize that on SQL server I could easily drop the [ in the above example and achieve the same query across both DB providers... that's not my point in this case)

What I would like to be able to do is use the InMemoryDatabase, but then mock setups for certain raw queries and assert that those queries are indeed executed. In the past I have achieved this using a mock of the database facade and much more, but this is brittle for a bunch of reasons: 1) Most of the stuff I needed to mock was internal which means I had to use reflection to mock it. This obviously caused it to break when EF Core was updated. I have a feeling this is a side effect of Microsoft's heavy use of extension methods, since while the extension method itself is public, it actually calls internal methods in the implementation making the extensions method quite cumbersome to mock. 2) Even if the methods I wanted to mock were public and unlikely to change from update to update, it doesn't help me when I want to selectively mock calls rather than mocking the entire facade. What I mean by this is that I want to use InMemory's key features MOST of the time (for example, when I am using Add() on a DbSet followed by SaveChanges on a DbContext), and I only want to have to mock raw queries.

I realize that I could make my own in-memory database implementation for testing, but this seems like it would be a nightmare and it really should be part of the core functionality in my honest opinion.

ajcvickers commented 4 years ago

@APIWT Implementing this would get complicated and messy fast, which is why we recommend using your actual database engine for testing for situations like this. That being said, I do think there is some value in being able to fake query results, and we'll consider what could be done here.

APIWT commented 4 years ago

@ajcvickers Thank you for your input. I think that your point about using the actual database engine for testing makes a lot of sense, but part of the issue with this is that we would have to spin up a docker container from inside our test class, and this is pretty tedious as far as I can tell. It would be really nice if that was something that was in the documentation because I would prefer it over the in-memory option in general.

I also really appreciate that you will consider being able to fake query results, this could be super helpful where a more accurate level of validation is less necessary and instead you just want to verify that a query has indeed been invoked.

Last but not least, I was able to temporarily work around this by introducing an interface called IRawQueryService which I am able to mock in my tests. I realize this isn't a perfect solution, but since it is an extremely thin wrapper on top of the raw-SQL commands for DbContext, I think it will work just fine for my use case until we need either production-system level validation or something similar.