如何在 EF Core 中模拟 EntityEntry?

问题描述 投票:0回答:1

我正在尝试为处理异常并重新加载 Entity Framework Core 中的实体的方法编写单元测试。该方法包含以下代码:

    catch (DbUpdateConcurrencyException)
    {
        await context.Entry(myEntityInstance).ReloadAsync();
    }
    catch { return null; };

我已成功模拟

SaveChangesAsync
并抛出所需的异常。但是,我在尝试模拟
Entry
方法时遇到了问题:

contextMock.Setup(c => c.Entry(It.IsAny<MyEntity>()).ReloadAsync(default))

这会导致

ArgumentException
:

无法实例化类的代理:Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry ...
找不到无参数构造函数。 (参数“constructorArguments”)---> System.MissingMethodException:未找到类型“Castle.Proxies.EntityEntry`1Proxy”的构造函数。

问题是

EntityEntry<TEntity>
不是模拟友好的,因为它只有一个构造函数:

[EntityFrameworkInternal]
public EntityEntry(InternalEntityEntry internalEntry)

InternalEntityEntry
是一个内部 EF Core 类,它依赖于其他内部类的实例。有一个 GitHub 问题 Trouble Mocking EntityEntry 讨论了这个问题,但 EF 团队似乎不打算进行任何更改。

如何在 EF Core 中模拟

EntityEntry
来有效测试此方法?

unit-testing entity-framework-core mocking moq ef-core-8.0
1个回答
0
投票

彻底检查

EF Core
源代码后,我创建了以下方法来模拟
EntityEntry
。该方法严重依赖内部API,但避免了反射。如果未来发生重大变化,可以轻松识别并纠正测试。

    /// <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;
    }

使用此方法的结果,您可以轻松地为

ReloadAsync
创建所需的模拟:

 entityEntryMock.Setup(e => e.ReloadAsync(default)).Returns(Task.CompletedTask);
© www.soinside.com 2019 - 2024. All rights reserved.