-
-
Notifications
You must be signed in to change notification settings - Fork 811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fast fail for internal interfaces makes unit testing Entity Framework break. #491
Comments
Hi @KarlZ. Thanks for taking the time to send a detailed error report, and for providing repro code that works right away. 👍
You are definitely right regarding the first (I'll tell you why in a second). But the latter seems unrelated. Is there a reason why you suspect it, too? I believe it is correct that Moq should let you know (by throwing an exception) when you're trying to set up something that it can't mock / intercept—such as inaccessible methods. But perhaps it shouldn't do the same (throw an exception) when it sets up all members of a type, or even a whole object graph, due to some internal automatism. In that latter case, perhaps it should simply ignore what it can't set up. The automatic setting up of all members of a type happens e.g. when you call Let's for example take your
Note your using a chained / dotted expression, set.Verify(people => people.Add(It.IsAny<Person>()), Times.Never); Your test would suddenly pass. Again, this is because processing such chained expressions can cause a hidden Please note that I am not recommending that you rewrite all of your tests. (Of course, if you want to use the latest version of Moq instead of waiting for a corrected version, the above should give you enough hints to fix your tests.) I suggest that perhaps Moq's current behaviour needs a minor adjustment, namely that it should complain about inaccessible members only if you've explicitly asked Moq to set up such a one. P.S.: I'm not 100 % convinced of my own last suggestion. Perhaps Moq is actually doing the right thing right already, and the real issue is either (a) that mocking |
I guess a quick question. You said, "...something that it can't mock/intercept -- such as inaccessible methods." But I think in our case, we are trying to mock an internal interface, so we should never reach the internal method? So is it OK to mock an internal interface, but not an internal method? |
Both is OK, if they are made accessible to Castle DynamicProxy (using the Note that members such as types and methods can be discoverable (using reflection) even when they are inaccessible. Inaccessible in the sense of what you can actually do with a member (calling it, overriding it, inheriting it, implementing it, etc.). Let me try to lay out the rules (and sorry if there are minor inaccuracies, there are several edge cases to consider that I might not remember right now):
In your specific case (tests involving Entity Framework), |
P.S.: If Entity Framework Core is an option for you at all, perhaps look into its InMemory provider, which can be used in testing scenarios instead of a real DB. Using that would possibly be a lot cleaner than mocking |
I guess I didn't answer your question,
The reason I suspect it is the combination of the two is that all our tests work on 127, which includes the exception. It isn't until 137 that our tests fail. So 4.7.99 all works... same on *.127, but *.137 and *.142 both cause these test failures. |
I see. If indeed #460 broke Entity Framework mocking, then I suspect that mocking Entity Framework
Perhaps I'm misunderstanding you, but the workaround is the same: get rid of chain expressions by splitting them up. For example, you can get your
with this: var people = new Mock<DbSet<Person>>();
people.Setup(p => p.Local).Returns(local);
context.Setup(c => c.People).Returns(() => people.Object); (Again, I'm not recommending that you rewrite all your tests that way, it would obviously affect easy of understanding. I only want to point out that it's feasible.) |
@KarlZ - I just published a pre-release version 4.8.0-rc1 on NuGet. The fail-fast issue when mocking Entity Framework types that you reported should now be fixed. Feel free to test. |
@stakx Works great! I'm looking forward to that release. Thanks for your help. |
@stakx My unit tests (>9000 of them) run in ~3 min on 4.8.0-rc1, but take over 7 minutes on 4.7.99. I went back and forth between these and have these times for 4.7.99 of 7:58 and 7:36, but on 4.8.0-rc1 have 2:50 and 3:03. I noticed back on October 20 that my test times more than doubled and have been blaming Visual Studio. (Reported here on Developer Community ) Curious and grateful! |
The most likely explanation for this is that you received the .NET Framework 4.7 (in-place) update around that time. .NET 4.7 ships with a regression that affected Moq very badly—see #500, which was posted on Oct 23). I then published a hotfix release (4.7.145; see #500 (comment)) that includes a workaround for this regression. If you want to avoid pre-release versions, you might be just fine with 4.7.145. That being said, I have been working on Moq's performance, and the final 4.8.0 should be even faster than the current pre-release! Just today, I discovered what appears to be a major bottleneck that Moq has suffered from since version 4.5.16. Right now, I am looking at a test suite that, once I apply my fix, ends up running twice as fast! 😮 I don't want to over-promise at this time; perhaps you won't see anything quite as drastic. Do stay tuned, though! 😀 |
It seems the change to cause "fast failure" for interfaces that are internal has caused many of our tests to fail. With the Entity Framework (EF) it seems Microsoft makes heavy use if internal interfaces and the previous code without the access check worked fine.
Borrowing from the work of others, we have a class called MockDbSet. This class allows us to setup and mock a DbSet in an EF DbContext. Before this strategy we had lots of code that could only be tested by hitting a real database. We currently have 2500 unit tests that leverage the MockDbSet class and they all work on 4.7.99 and 4.7.127. But 250 unit tests fail with 4.7.137 and higher. It seems that the call to ThrowIfSetupMethodNotVisibleToProxyFactory (127) combined with "always returning the exact same proxy object" (137) causes this.
Is there a suggested pattern to follow to allow MockDbSet to continue to work? If not, is there a way we could override the behavior and allow the use of internal interfaces?
Here is the code for MockDbSet:
Attached is a very simple test project with one class library that has demonstration tests that run fine with Moq 4.7.127 but fail with Moq 4.7.137.
MockDbSetIssue.zip
The text was updated successfully, but these errors were encountered: