0

I am using moq version 4.2 to unit test some controllers of web api 2 with EF as the ORM. Since a lot of tests need a mocked IEntityRepository<User> so I use a static method in a separate class called TestHelper to create such a fake repository so that I can just call this method in all the tests where needed. Here is the static method:

internal static IEntityRepository<User> GetSingleUserMockRepository(User user, bool noTracking = true, params Expression<Func<User, object>>[] properties)
{
            var userRepository = new Mock<IEntityRepository<User>>();
            if (properties.Any())
                userRepository.Setup(x => x.GetSingleInclude(user.Key, noTracking, properties)).Returns(Task.FromResult(user));
            else
                userRepository.Setup(x => x.GetSingle(user.Key, noTracking)).Returns(Task.FromResult(user));

            return userRepository.Object;
}

I am not showing other method details here because don't think they're relevant to my problem. The thing is when calling this static method in my tests and pass the returned user repository into controllers the test will fail because it cannot find the user i.e. Setup didn't work!. However, if I repeat the test implementation within my tests to create the mock repository as a local variable; everything works fine.

Namely, this doesn't work (where Devices is a navigation property on the User entity, again don't think the detail matters here) e.g. if I pass the returned userRepository into my controller the test will fail.:

var userRepository = TestHelper.GetSingleUserMockRepository(user, true, x=> x.Devices);

But this works e.g. if I just use a local variable in the test and pass the userRepository.Object into my controller everything works as expected:

var userRepository = new Mock<IEntityRepository<User>>();
userRepository.Setup(x => x.GetSingleInclude(user.Key, true, u => u.Devices)).Returns(Task.FromResult(user));

Can someone please explain why? I thought these two ways are equivalent but clearly not in practice.

dragonfly02
  • 3,403
  • 32
  • 55

1 Answers1

1

This is maybe not a full answer (yet), but still to long to fit into a Stack Overflow comment.

When you do:

userRepository.Setup(x => x.GetSingleInclude(user.Key, noTracking, properties)).Returns(...);

you are saying: When GetSingleInclude is called with that user.Key and that noTracking and in particular that properties which is an array of expression trees, Moq shall return what you specify.

However, if Moq gets a call to GetSingleInclude where one or more of the arguments are not equal to the values you supplied, your setup is not relevant, and Moq returns instead the default value which is null. (If you had used MockBehavior.Strict instead, it would have thrown an exception instead of silently just returning null, which could be more helpful for understanding the issue.)

So when are two arrays of expression trees equal? If Moq uses the default equality comparer for arrays, they are only "equal" when they are the same array instance. If Moq uses some entry-wise comparison, it will come down to whether each expression tree is equal as reference (i.e. same Expression<> instance). In any case you may have trouble.

The question is why it works in the other case. Does GetSingleInclude have special overloads for when the number of properties (expression trees) is little?

What is the result of running the code from this question?

I am saying that you may be facing the same problem as in the thread Mocking and verifying call to method containing an Expression<Func<T,bool>> parameter.

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • I agree that expression tree by default uses reference equality comparison hence the `Setup` will fail because the expression tree instance passed into the `Setup` will not be equal to the instance passed into the method on the repository. But why does it work when the mocked repository is created using a local variable in the test? This should fail as well. – dragonfly02 Dec 05 '15 at 21:16
  • @stt106 I also do not understand that. But I think we need to see more code for your local-variable case where it works, to be able to answer. Maybe you can reduce your scenario to a small/minimal example which is fully reproducible for us. – Jeppe Stig Nielsen Dec 05 '15 at 23:00
  • OK will create a simpler demo version once finished Xmas shopping:) – dragonfly02 Dec 06 '15 at 11:38
  • @stt106 Sometimes the process of creating a small reproducible demonstration even leads to the answer to what one is about to ask (or has asked). – Jeppe Stig Nielsen Dec 06 '15 at 23:00
  • @stt106 Note: In some cases the same delegate instance *may* be re-used, and this is probably a part of the explanation of how you could have an example where this worked. See the thread *[Why are lambda expressions not “interned”?](http://stackoverflow.com/questions/4807808/)* for details. ***Addition:*** That thread is about lambdas turned into plain delegate instances. Our thread is lambdas re-written to expression trees. – Jeppe Stig Nielsen Dec 09 '15 at 09:20
  • Thanks for the continued efforts in resolving this! Apologies for not having time to replicate the problem with a simpler code base. Will definitely look into the threads you mentioned and report any progress about the problem. – dragonfly02 Dec 09 '15 at 11:19