2

I've written a test to validate a small piece of new functionality which calls a large middleware function with a ton of logic in it that converts one type of User (an ASP Net Membership User) to an internal one (SystemUser). It's a messy system, but I can't rip it out, hence the new tests.

My test calls into a GetAllActiveUsers() method which calls down a few layers and eventually gets to an enumeration that's causing problems. The test has a mocked up entity framework datacontext Mock<localEntities> mockDb which is Setup as follows:

[SetUp]
public void Setup()
    {
        var mockUserSet = new Mock<DbSet<User>>();

        this.mockDb = new Mock<localEntities>();
        this.mockMembershipService = new Mock<IMembershipService>();
        this.mockRoleService = new Mock<IRoleService>();
        this.mockUserAgreementRelation = new Mock<DbSet<UserAgreementRelation>>();

        this.testUsers = this.CreateSystemUsers();

        this.mockDb.Setup(m => m.User).Returns(mockUserSet.Object);

        mockUserAgreementRelation.SetupAllProperties();

        var agreementRelations = this.testUsers.Select(u => new UserAgreementRelation() { AgentUserID = u.UserId, IsDeleted = null });

        mockUserAgreementRelation.As<IQueryable<UserAgreementRelation>>().Setup(ua => ua.GetEnumerator()).Returns(agreementRelations.GetEnumerator());
        this.mockDb.Setup(m => m.UserAgreementRelation).Returns(mockUserAgreementRelation.Object);
    }

In my test I setup some users and call into var testActiveUsers = testRepo.GetAllActiveUsers(); which calls the problem line.

Here's where I get confused.

The test passes with this code:

var agreementRelations = databaseContext.UserAgreementRelation().ToList();

var userAgreementFromDB = agreementRelations
            .FirstOrDefault(x => x.AgentUserID == ramsUser.UserId && (x.IsDeleted == null || !(bool)x.IsDeleted));

but fails with this code:

var userAgreementFromDB = databaseContext.UserAgreementRelation
            .FirstOrDefault(x => x.AgentUserID == ramsUser.UserId && (x.IsDeleted == null || !(bool)x.IsDeleted));

Additionally, if I debug and step through the code, I can use a quick watch on the mocked UserAgreementRelation once. The first time I will see a UserAgreementRelation for each user I'm testing. If I run the watch again the enumeration yields no results even though the code hasn't advanced. I'm guessing this is related to the issue. However, since I've no idea why it's happening, I can't say how it might cause this problem.

Keep in mind that the simpler code (without the ToList()) is production code. It's currently functioning fine, but my test fails. Since this is a direct database call, enumerating the entire UserAgreementRelation table isn't a viable solution.

Suggestions?

Necoras
  • 6,743
  • 3
  • 24
  • 45

2 Answers2

5

I am not sure I get the problem, for you do not give details on how it "fails". But maybe you should change the part:

.Setup(ua => ua.GetEnumerator()).Returns(agreementRelations.GetEnumerator())

into:

.Setup(ua => ua.GetEnumerator()).Returns(() => agreementRelations.GetEnumerator())

The reason is that if the code you test calls GetEnumerator() more than once, Moq should not hand out the old "used" enumerator instance which is probably already advanced to the end and maybe disposed. Instead Moq should re-run a Func<>, my arrow () => agreementRelations.GetEnumerator(), to obtain a fresh enumerator which has not yet been advanced (MoveNext()) or closed (Dispose()).


The asker informs me (in comment below) that he also needed to Setup the properties Provider and Expression of IQueryable.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • You're correct that that was a bug in my code. I've corrected it, and can now see the contents of the Enumeration more than once. However, I'm still getting the ever frustrating "Object not set to an instance of an object" error on the FirstOrDefault Linq statement. – Necoras Mar 03 '16 at 16:26
  • I'm marking this as the answer, because it does fix the disappearing enumerator. However, I'm still at a loss as to why my test is failing. – Necoras Mar 03 '16 at 16:37
  • @Necoras You need to give more details on how it is failing. Do you get an exception from Moq? Does `userAgreementFromDB` become null? Please include all details you can. It will help people see where your problem comes from. – Jeppe Stig Nielsen Mar 03 '16 at 16:45
  • When running actual code, databaseContext.UserAgreementRelation is a "DbSet". When run by the test it is a Moq object, that is "{Castle.Proxies.DbSet`1Proxy_1}". The NRE occurs at System.Linq.Queryable.FirstOrDefault in the stack trace, making me think that the Unit test sees the UserAgreementRelation object as null. That confuses me since I can see an enumerable object there while debugging. – Necoras Mar 03 '16 at 19:06
  • I found the solution. I needed my mock object to have Setups for the Provider and Expression values as well as detailed here: http://stackoverflow.com/questions/20002873/entity-framework-6-mocking-include-method-on-dbset. Thanks for the help. – Necoras Mar 03 '16 at 20:40
  • 1
    @Necoras OK, I mentioned that in my answer. Feel free to improve my answer (by editing it), if relevant. – Jeppe Stig Nielsen Mar 03 '16 at 22:16
0

I've always used Where for stuff like this, try this.

var userAgreementFromDB = databaseContext.UserAgreementRelation
            .Where(x => x.AgentUserID == ramsUser.UserId && (x.IsDeleted == null || !(bool)x.IsDeleted)).FirstOrDefault();
Seano666
  • 2,238
  • 1
  • 15
  • 14
  • I believe that is functionally equivalent to my code. Regardless, the results are the same: a Null Reference Exception on that line. – Necoras Mar 03 '16 at 16:31
  • Is databaseContext.UserAgreementRelation an IEnumerable? If ToList()'ing it before works, but the same logic on the IEnumerable is throwing an error, then it makes sense that you are trying to do something to the IEnumerable that is not allowed. Seems like the way to discover the problem would be to have a foreach loop on databaseContext.UserAgreementRelation and try applying your checks inside. – Seano666 Mar 03 '16 at 17:19
  • 1
    See my comments above. Thanks for the help. – Necoras Mar 03 '16 at 20:41