Skip to content
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

Issue mocking Entity Framework since 4.7.58 #441

Closed
EricStG opened this issue Sep 14, 2017 · 7 comments
Closed

Issue mocking Entity Framework since 4.7.58 #441

EricStG opened this issue Sep 14, 2017 · 7 comments

Comments

@EricStG
Copy link

EricStG commented Sep 14, 2017

Looks like I'm having the same issue as #383, except that the fix didn't work for me.

Specifically, I'm using the Entity Framework testing library for Moq (https://github.com/scott-xu/EntityFramework.Testing/blob/master/src/EntityFramework.Testing.Moq)

When trying to Moq a DbSet with Moq >= 4.7.58, I'm getting exceptions. Tested with Moq 4.7.58, 4.7.63 and 4.7.99. Works in Moq 4.7.49.

System.NotImplementedException : The member 'IQueryable.Provider' has not been implemented on type 'DbSet`1Proxy' which inherits from 'DbSet`1'. Test doubles for 'DbSet`1' must provide implementations of methods and properties that are used.
   at System.Data.Entity.Infrastructure.DbQuery`1.GetInternalQueryWithCheck(String memberName)
   at System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
   at System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
   at Ns.Store.<GetChildrenAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Ns.StoreTests.<GetChildrenAsync_NoMatchingProduct_ReturnsEmptyArray>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at NUnit.Framework.Internal.AsyncInvocationRegion.AsyncTaskInvocationRegion.WaitForPendingOperationsToComplete(Object invocationResult)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunAsyncTestMethod(TestExecutionContext context)
@stakx
Copy link
Contributor

stakx commented Sep 14, 2017

Hi @EricStG, it's a bit difficult to help without seeing any of your code that triggers the exception. Could you please post a minimally complete repro code or unit test that causes the error?

@EricStG
Copy link
Author

EricStG commented Sep 14, 2017

Hi @stakx, you can find a minimal repro at https://github.com/EricStG/TestEFMoq

@stakx stakx removed the needs-repro label Sep 16, 2017
@stakx
Copy link
Contributor

stakx commented Sep 16, 2017

@EricStG: Thanks for the repro code.

Looks like I'm having the same issue as #383, except that the fix didn't work for me.

Your issue is similar, but not identical to the one you mentioned because your repro code makes use of an extension method mock.SetupData, which is not part of Moq, but comes with Entity Framework. I'm not familiar with that extension method. I suspect that there's nothing to be done at Moq's end. This might have to be fixed in the EF testing library. I'll look into it some more and report back with my findings.

@stakx
Copy link
Contributor

stakx commented Sep 16, 2017

@EricStG: OK, here are two possible fixes for your failing unit test:

var mockDeclaredProductSet = new Mock<DbSet<Product>>();
var mockContext = new Mock<Context>();
mockContext.Setup(c => c.DeclaredProducts).Returns(mockDeclaredProductSet.Object);   // (*)

var declaredProducts = new List<Product>();
mockDeclaredProductSet.SetupData(declaredProducts);

var dummy = mockContext.Object.DeclaredProducts.FirstOrDefault();

1. Completely set up mockDeclaredProductSet before you use it in (*):

var mockDeclaredProductSet = new Mock<DbSet<Product>>();
var declaredProducts = new List<Product>();  // (1)
mockDeclaredProductSet.SetupData(declaredProducts);  // (2)

var mockContext = new Mock<Context>();
mockContext.Setup(c => c.DeclaredProducts).Returns(mockDeclaredProductSet.Object);   // (*)

var dummy = mockContext.Object.DeclaredProducts.FirstOrDefault();

Note that the two lines of code marked (1) and (2) have been moved up, before (*).

2. In (*), turn the argument passed to .Returns into a lambda:

var mockDeclaredProductSet = new Mock<DbSet<Product>>();
var mockContext = new Mock<Context>();
mockContext.Setup(c => c.DeclaredProducts).Returns(() => mockDeclaredProductSet.Object);   // (*)

var declaredProducts = new List<Product>();  // (1)
mockDeclaredProductSet.SetupData(declaredProducts);  // (2)

var dummy = mockContext.Object.DeclaredProducts.FirstOrDefault();

Note the introduction of () => in the line of code marked with (*). This effectively means that the return value gets lazily evaluated, the lines marked with (1) and (2) will end up running first.

Brief background explanation:

Upon the first call to .Object on a mock, Moq generates a proxy object for that mock. At this exact moment, Moq needs to know which interfaces the proxy object should "implement", or be able to intercept. Entity Framework's DbSet<TEntity> itself implements a lot of interfaces, among them IQueryable, IQueryable<T>, IEnumerable, IEnumerable<T>. Moq will only know that the generated proxy should implement these interfaces if you provide at least one setup (e.g. via mock.As<IQueryable>().Setup(...)). If you don't have any setups, and then call mock.Object, a proxy gets generated that doesn't implement any of these interfaces, therefore you'll get a NotImplementedException.

Making sure that lines (1) and (2) run before line (*) (which is the line triggering proxy object generation) has the effect of calling .SetupData, which will perform the necessary setups on the various interfaces implemented by DbSet<TEntity>. These setups in turn tell Moq that the generated proxy should implement the interfaces required by DbSet.

(If you want more details on why order of execution matters here, check out the source code for mock.SetupData and see this PR's description.)

Please let me know whether the above resolves your issue.

@EricStG
Copy link
Author

EricStG commented Sep 16, 2017

@stakx That should solve the issue. Thanks for looking into this.

@EricStG EricStG closed this as completed Sep 16, 2017
@ajgoldenwings
Copy link

I spent about a half a day figuring out this. Option two fixed this for me. Thank you.

I changed
mockContext.SetupGet(c => c.Organizations).Returns(_mockOrganizations.Object);
to
mockContext.Setup(c => c.Organizations).Returns(() => _mockOrganizations.Object);

@mohamedabotir
Copy link

thanks second fix work for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants