【问题标题】:Mocking DbSet<T> inline模拟 DbSet<T> 内联
【发布时间】:2015-12-17 10:52:27
【问题描述】:

我正在使用.NET4.5EF6Moq 进行单元测试。我正在尝试模拟一些 Db 数据进行测试。我有一个示例如何通过声明 mockset as variable and then using mocks.

来做到这一点
public static class TestExtensionMethods
{
       public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
       {
           var queryable = sourceList.AsQueryable();

           var dbSet = new Mock<DbSet<T>>();
           dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
           dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
           dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
           dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
           dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
           return dbSet.Object;
       }
}

我将它用作扩展方法 ATM,例如:mockedDbContext.Journey = new List&lt;Journey&gt; { }.AsDbSet();

有没有办法将其全部声明为单行(这样我就可以将数据库集传递为TestCaseData 而无需编写扩展方法)。我试过了

var mockedDbContext = new Mock<OnlineLegal>();
mockedDbContext.Setup(o => o.Journey).Returns(() => (DbSet<Journey>)(new List<Journey> { new Journey { SessionId = sessionId, ConveyancingAnswer = new Collection<ConveyancingAnswer>()} }.AsEnumerable()));

但它被System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.List1[Saga.Services.Legal.Website.Journey]' to type 'System.Data.Entity.DbSet1[Saga.Services.Legal.Website.Journey]'.炸毁了

如何模拟DbSet&lt;T&gt; inline?

编辑:关于重复,标记我的问题的人除了标题之外没有花更多的精力阅读,今年将从圣诞老人那里得到沙林毒气。

【问题讨论】:

标签: c# unit-testing entity-framework-6 moq dbset


【解决方案1】:

在这种情况下,使用您定义的方法从 List 创建 DbSet,其中 T 的类型为 Journey,并假设 o.Journey 的类型为 DbSet&lt;Journey&gt;

private DbSet<T> ToDbSet<T>(List<T> sourceList) where T : class
{
    var queryable = sourceList.AsQueryable();

    var dbSet = new Mock<DbSet<T>>();
    dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
    dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
    dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
    dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
    dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
    return dbSet.Object;
}

private void SomeOtherMethod() 
{
    var journey = new Journey 
    { 
        SessionId = sessionId, 
        ConveyancingAnswer = new Collection<ConveyancingAnswer>()
    };
    var journeys = new List<Journey> { journey };  
    mockedDbContext.Setup(o => o.Journey)
                   .Returns(() => ToDbSet<Journey>(journeys));
}

如果你想把它放在一行上,那么它是可能的,但在可读性方面不一定是可取的。

【讨论】:

  • 嗨,silleknarf 感谢您的回复。在您的答案中,我可以在哪里找到在不使用扩展方法的情况下将 DbSet 声明为内联?
  • TestExtensionMethods.AsDbSet&lt;Journey&gt;(journeys) 返回一个DbSet
  • 我现在已经在我的回答中删除了扩展方法的使用
  • sillknarf 我非常感谢您的努力,并且绝不会阻止您,但您的回答只是重复了我的问题并进行了一些转换,我正在寻找的是如何在没有必须编写额外的机制,比如声明列表然后以某种方式转换它,可以像new List&lt;T&gt;().Select(o =&gt; new DbSet(o)) 或类似的那样简单,但是 DbSet 构造函数是内部的,所以问题非常复杂,可能根本没有办法做到这一点。不过感谢您的尝试。
【解决方案2】:

昨天我已经研究这个话题几个小时了。 @silleknarf 的答案是正确的,但并不完整。异步方法将给出异常。我昨天在网上搜索并找到了答案,并将这种扩展方法作为解决方案。它适用于 dotnet 3.1。由于对接口进行了一些更改,以前的版本可能需要不同的实现。希望它对你们中的一些人有用。 :)

public static class DbSet
{
    public static DbSet<T> ToDbSet<T>(this List<T> sourceList) where T : class
    {
        var queryable = sourceList.AsQueryable();
        var dbSet = new Mock<DbSet<T>>();

        dbSet.As<IAsyncEnumerable<T>>()
             .Setup(m => m.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
             .Returns(new MockAsyncEnumerator<T>(queryable.GetEnumerator()));

        dbSet.As<IQueryable<T>>()
            .Setup(m => m.Provider)
            .Returns(new MockAsyncQueryProvider<T>(queryable.Provider));

        dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
        dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
        return dbSet.Object;
    }
    private class MockAsyncQueryProvider<TEntity> : IAsyncQueryProvider
    {
        private readonly IQueryProvider _inner;

        internal MockAsyncQueryProvider(IQueryProvider inner)
        {
            _inner = inner;
        }

        public IQueryable CreateQuery(Expression expression)
        {
            return new MockAsyncEnumerable<TEntity>(expression);
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new MockAsyncEnumerable<TElement>(expression);
        }

        public object Execute(Expression expression)
        {
            return _inner.Execute(expression);
        }

        public TResult Execute<TResult>(Expression expression)
        {
            return _inner.Execute<TResult>(expression);
        }

        public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
        {
            var expectedResultType = typeof(TResult).GetGenericArguments()[0];
            var executionResult = typeof(IQueryProvider)
                     .GetMethod(name: nameof(IQueryProvider.Execute), genericParameterCount: 1, types: new[] { typeof(Expression) })
                     .MakeGenericMethod(expectedResultType)
                     .Invoke(this, new[] {expression});

            return (TResult)typeof(Task)
                .GetMethod(nameof(Task.FromResult))
                ?.MakeGenericMethod(expectedResultType)
                .Invoke(null, new[] { executionResult });
        }
    }
    private class MockAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
    {
        IQueryProvider IQueryable.Provider => new MockAsyncQueryProvider<T>(this);

        public MockAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { } 
        public MockAsyncEnumerable(Expression expression) : base(expression) { } 

        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
        {
            return new MockAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
        }
    }
    private class MockAsyncEnumerator<T> : IAsyncEnumerator<T>
    {
        private readonly IEnumerator<T> _inner;

        public T Current => _inner.Current;

        public MockAsyncEnumerator(IEnumerator<T> inner)
        {
            _inner = inner;
        }

        public ValueTask<bool> MoveNextAsync()
        {
            return new ValueTask<bool>(_inner.MoveNext());
        }
        public ValueTask DisposeAsync()
        {
            _inner.Dispose();

            return new ValueTask();
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 2021-04-07
    • 2020-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-30
    相关资源
    最近更新 更多