rgvlee/EntityFrameworkCore.Testing

System.ArgumentNullException : Value cannot be null. (Parameter 'mockedQueryable')

pablo-daza-incontact opened this issue · 5 comments

Hello,

I really appreciate this work is really helpful.

I would like to ask if there can be an issue with Pomelo Entity framework.

I've recently upgrade my asp .net core implementation to .net core 3.1 and pomelo package to 3.1.1 and it showed up an error in my tests with this package.

When the test tries to mock FromSqlRaw or FromSqlInterpolatedResults I received the following error message:
var cont = Create.MockedDbContextFor<AutoAttendantContext>(); cont.Set<LockStatus>().AddFromSqlInterpolatedResult($"SELECT COALESCE(GET_LOCK({lockKey}, 0), 0) AS status", queryResult);

Message:
System.ArgumentNullException : Value cannot be null. (Parameter 'mockedQueryable')
Stack Trace:
EnsureArgument.IsNotNull[T](T argument, String argumentName)
QueryableExtensions.AddFromSqlInterpolatedResult[T](IQueryable1 mockedQueryable, FormattableString sql, IEnumerable1 fromSqlInterpolatedResult)
MySqlLockRepositoryTest.SetLockValue_WhenLockValueCanBeClaimed_ShouldReturnTrue() line 95
GenericAdapter1.GetResult() AsyncToSyncAdapter.Await(Func1 invoke)
TestMethodCommand.RunTestMethod(TestExecutionContext context)
TestMethodCommand.Execute(TestExecutionContext context)
<>c__DisplayClass1_0.b__0()
BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)

Can you help me please ?

I haven't done any testing with Pomelo (assuming we're referring to https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql), however that shouldn't be an issue as you aren't using that for the tests. Under the covers you're using mocked objects which in part wrap over the MS in-memory provider.

Given it's a null reference exception while setting up the mock, I'd be interested to see how you've wired up LockStatus in AutoAttendantContext. cont.Set<LockStatus>() is returning null which means Create.MockedDbContextFor hasn't been able to get LockStatus from the list of models registered with the DbContext and initialize it. I use DbContext.Model.GetEntityTypes() to do the latter.

If you can give me the above relevant code from AutoAttendantContext (don't have to provide everything; just the blocks that relate to LockStatus should be enough) I can look into it further.

Thank you for your help !
I found a fix for the issue, I added the following code in the context and now everything is working as expected.
public virtual DbSet<LockStatus> LockResult { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<LockStatus>().HasNoKey();

JFYI: before updating to .net core 3.1 from .net core 2.2 the lock status was set as DbQuery because we are making a query using FromRawSql to get the LockStatus Model from some specific mysql queries not related to tables but now DbQuery is marked as Obsolete. Now we are using DbSet but we had to add the annotation [NotMapped] and the method HasNoKey() in to the model to let the testing not return null in the mocked context.

I'd expect it to be a context set property otherwise it won't be included in the context model. If it's not, at least in terms of the in-memory provider, it throws an the InvalidOperationException "Cannot create a DbSet for 'LockStatus' because this type is not included in the model for the context.". That exception isn't surfacing, rather it's returning null as there is no set up for it. I'll attempt to add a setup to return this exception so you get a better error message.

Is this functionality that has worked in production code before? Populating a model that doesn't have a set/query property in the context? If so, what versions of EFCore/Pomelo.

Regardless of the above I have a doco update coming soon and I will be adding a note about the virtual set properties in the requirements section. It's one of the drawbacks of mocking a concrete class.

RE the NotMapped annotation and the HasNoKey fluent configuration; usually you only have to do one or the other WRT how you configure the context. Favour the fluent configuration if you can.

I have deployed a patch release which includes a catch all set up handler so if you attempt to do something on an entity that is not included in the DbContext model, you'll get the same InvalidOperationException message as you would from the in-memory provider. Should hopefully make it a bit clearer when something isn't right. Feel free to test and let me know if it would have helped your initial diagnosis.

I'm going to leave this issue open until I have updated the documentation as mentioned above.

I have updated the documentation with a note regarding virtual properties.