I am trying to write a unit test for a method that handles exceptions and reloads an entity in Entity Framework Core. The method contains the following code:
catch (DbUpdateConcurrencyException)
{
await context.Entry(myEntityInstance).ReloadAsync();
}
catch { return null; };
I have successfully mocked SaveChangesAsync
to throw the desired exception. However, I am encountering issues when attempting to mock the Entry
method:
contextMock.Setup(c => c.Entry(It.IsAny<MyEntity>()).ReloadAsync(default))
This results in an ArgumentException
:
Can not instantiate proxy of class: Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry ...
Could not find a parameterless constructor. (Parameter 'constructorArguments') ---> System.MissingMethodException: Constructor on type 'Castle.Proxies.EntityEntry`1Proxy' not found.
The problem is that EntityEntry<TEntity>
is not mock-friendly because it has only one constructor:
[EntityFrameworkInternal]
public EntityEntry(InternalEntityEntry internalEntry)
InternalEntityEntry
is an internal EF Core class that depends on instances of other internal classes. There is a GitHub issue Trouble Mocking EntityEntry discussing this problem, but it seems that the EF team is not planning to make any changes.
How can I mock EntityEntry
in EF Core to test this method effectively?
After thoroughly examining the EF Core
source code, I created the following method to mock EntityEntry
. This method relies heavily on internal APIs, but it avoids reflection. If a future breaking change occurs, it can be easily identified and the test can be corrected.
/// <summary>
/// Mocks an <see cref="EntityEntry"/> for a given entity in the specified DbContext.
/// </summary>
/// <typeparam name="TContext">The type of the DbContext.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="dbContextMock">The mock of the DbContext.</param>
/// <param name="entity">The entity to mock the entry for.</param>
/// <returns>A <see cref="Mock"> of the <see cref="EntityEntry"/> for the specified entity.</returns>
/// <remarks>The method heavilly depends on "Internal EF Core API" and so can fail after EF upgrade. Use with extreme care.</remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<Pending>")]
public static Mock<EntityEntry<TEntity>> MockEntityEntry<TContext, TEntity>(Mock<TContext> dbContextMock, TEntity entity)
where TContext : DbContext
where TEntity : class
{
var stateManagerMock = new Mock<IStateManager>();
stateManagerMock
.Setup(x => x.CreateEntityFinder(It.IsAny<IEntityType>()))
.Returns(new Mock<IEntityFinder>().Object);
stateManagerMock
.Setup(x => x.ValueGenerationManager)
.Returns(new Mock<IValueGenerationManager>().Object);
stateManagerMock
.Setup(x => x.InternalEntityEntryNotifier)
.Returns(new Mock<IInternalEntityEntryNotifier>().Object);
var entityTypeMock = new Mock<IRuntimeEntityType>();
var keyMock = new Mock<IKey>();
keyMock
.Setup(x => x.Properties)
.Returns([]);
entityTypeMock
.Setup(x => x.FindPrimaryKey())
.Returns(keyMock.Object);
entityTypeMock
.Setup(e => e.EmptyShadowValuesFactory)
.Returns(() => new Mock<ISnapshot>().Object);
var internalEntityEntry = new InternalEntityEntry(stateManagerMock.Object, entityTypeMock.Object, entity);
var entityEntryMock = new Mock<EntityEntry<TEntity>>(internalEntityEntry);
dbContextMock
.Setup(c => c.Entry(It.IsAny<TEntity>()))
.Returns(() => entityEntryMock.Object);
return entityEntryMock;
}
Using the result of this method, you can easily create the desired mock for ReloadAsync
:
entityEntryMock.Setup(e => e.ReloadAsync(default)).Returns(Task.CompletedTask);