如何将此EF Mock设置代码编写为可重复使用的通用Boilerplate

本文关键字:Boilerplate EF Mock 代码 设置 | 更新日期: 2023-09-27 18:21:34

我使用的是moq、ef 6和xunit。我发现自己一遍又一遍地写这段代码,心想也许我可以把它变成一个通用方法,但遇到了一些麻烦。

public static void CreateSalesMock(List<Sale> sales, Mock<DatabaseContext> dbContextMock)
{
    var data = sales.AsQueryable();
    var mockSet = new Mock<DbSet<Sale>>();
    mockSet.As<IQueryable<Sale>>()
           .Setup(x => x.Provider)
           .Returns(data.Provider);
    mockSet.As<IQueryable<Sale>>()
           .Setup(x => x.Expression)
           .Returns(data.Expression);
    mockSet.As<IQueryable<Sale>>()
           .Setup(x => x.ElementType)
           .Returns(data.ElementType);
    mockSet.As<IQueryable<Sale>>()
           .Setup(x => x.GetEnumerator())
           .Returns(data.GetEnumerator());
    dbContextMock.Setup(x => x.Sales).Returns(mockSet.Object);
}

现在,我的数据库中还有许多其他表,所以如果我能写一个方法,接收这些数据的列表并设置它,这样我就可以模拟查询了,那就太好了。

public static void CreateMockSet<T, TA, TB>(T dataList, TA model, 
    Func<TB> lambda, Mock<DatabaseContext> dbContextMock) 
     where T : List<T>
     where TA: Mock<DbSet<TA>>
{
    var data = dataList.AsQueryable();
    model.As<IQueryable<T>>()
         .Setup(x => x.Provider)
         .Returns(data.Provider);
    model.As<IQueryable<T>>()
         .Setup(x => x.Expression)
         .Returns(data.Expression);
    model.As<IQueryable<T>>()
         .Setup(x => x.ElementType)
         .Returns(data.ElementType);
    model.As<IQueryable<T>>()
         .Setup(x => x.GetEnumerator())
         .Returns(data.GetEnumerator());
    dbContextMock.Setup(x => lambda);
}

到目前为止,我已经知道了,但我不确定这是否有效。我一直在通过"lambda"部分(例如x => x.Sales),所以我甚至无法测试它。

如何将此EF Mock设置代码编写为可重复使用的通用Boilerplate

Tim Larson已经在他的博客中为这个样板代码提供了一个很好的解决方案:

public static class DbSetMocking
{
    private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data)
            where T : class
    {
        var queryableData = data.AsQueryable();
        var mockSet = new Mock<DbSet<T>>();
        mockSet.As<IQueryable<T>>().Setup(m => m.Provider)
                .Returns(queryableData.Provider);
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression)
                .Returns(queryableData.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType)
                .Returns(queryableData.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
                .Returns(queryableData.GetEnumerator());
        return mockSet;
    }
    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
            this IReturns<TContext, DbSet<TEntity>> setup,
            TEntity[] entities)
        where TEntity : class
        where TContext : DbContext
    {
        Mock<DbSet<TEntity>> mockSet;
        return ReturnsDbSet(setup, entities, out mockSet);
    }
    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
            this IReturns<TContext, DbSet<TEntity>> setup,
            IQueryable<TEntity> entities)
        where TEntity : class
        where TContext : DbContext
    {
        Mock<DbSet<TEntity>> mockSet;
        return ReturnsDbSet(setup, entities, out mockSet);
    }
    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
            this IReturns<TContext, DbSet<TEntity>> setup,
            IEnumerable<TEntity> entities)
        where TEntity : class
        where TContext : DbContext
    {
        Mock<DbSet<TEntity>> mockSet;
        return ReturnsDbSet(setup, entities, out mockSet);
    }
    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
    this IReturns<TContext, DbSet<TEntity>> setup,
    TEntity[] entities, out Mock<DbSet<TEntity>> mockSet)
        where TEntity : class
        where TContext : DbContext
    {
        mockSet = CreateMockSet(entities.AsQueryable());
        return setup.Returns(mockSet.Object);
    }
    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
            this IReturns<TContext, DbSet<TEntity>> setup,
            IQueryable<TEntity> entities, out Mock<DbSet<TEntity>> mockSet)
        where TEntity : class
        where TContext : DbContext
    {
        mockSet = CreateMockSet(entities);
        return setup.Returns(mockSet.Object);
    }
    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
    this IReturns<TContext, DbSet<TEntity>> setup,
    IEnumerable<TEntity> entities, out Mock<DbSet<TEntity>> mockSet)
        where TEntity : class
        where TContext : DbContext
    {
        mockSet = CreateMockSet(entities.AsQueryable());
        return setup.Returns(mockSet.Object);
    }
}

然后在UT中,您使用它如下:

var context = new Mock<DatabaseContext>();
context.setup(x => x.Sales).ReturnsDbSet(new List<Sale>(){put here the items..});

编辑

我更新了代码。现在又有3个重载允许对DbSet<T>属性进行验证:

    [TestMethod]
    public void TestMethod1()
    {
        var sales = new List<Sale>
        {
            new Sale() {id = 1},
            new Sale() {id = 6},
            new Sale() {id = 5},
            new Sale() {id = 4},
            new Sale() {id = 3},
            new Sale() {id = 2}
        };
        var fakeContest = new Mock<SalesContext>();
        Mock<DbSet<Sale>> fakeSet;
        fakeContest.Setup(context => context.Sales).ReturnsDbSet(sales, out fakeSet);
        var itemsToRemove = sales.Where(sale => sale.id%2 == 0);

        fakeContest.Object.Sales.RemoveRange(itemsToRemove);

        fakeSet.Verify(set => set.RemoveRange(itemsToRemove));
    }