在XUnit中用Moq怎样模拟EntityFramework Core下的DbSet

最近在做一个项目的单元测试时,遇到了些问题,解决后,觉得有必要记下来,并分享给需要的人,先简单说一下项目技术框架背景:

  • asp.net core 2.0(for .net core)框架

  • 用Entity Framework Core作ORM

  • XUnit作单元测试

  • Moq作隔离框加

 在对业务层进行单元测试时,因为业务层调用到数据处理层,所以要用Moq去模拟DbContext,这个很容易做到,但如果操作DbContext下的DbSet和DbSet下的扩展方法时,就会抛出一个System.NotSupportedException异常。这是因为我们没办法Mock DbSet,并助DbSet是个抽象类,还没有办法实例化。

其实,这个时候我们希望的是,如果用一个通用的集合,比如List<T>集合,或T[]数组来Mock DbSet<T>,就非常舒服了,因为集合或数组的元素我们非常容易模拟或控制,不像DbSet。

深挖DbSet下常用的这些扩展方法:Where,Select,SingleOrDefault,FirstOrDefault,OrderBy等,都是对IQueryable的扩展,也就是说把对DbSet的这些扩展方法的调用转成Mock List<T>或T[]的扩展方法调用就OK了,

所以实现下的类型:

项目需要引入:Microsoft.EntityFrameworkCore 和Moq,Nuget可以引入。

UnitTestAsyncEnumerable.cs

using System.Collections.Generic;

using System.Linq;

using System.Linq.Expressions;


namespace MoqEFCoreExtension

{

    /// <summary>

    /// 自定义实现EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>类型

    /// </summary>

    /// <typeparam name="T"></typeparam>

    class UnitTestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>

    {

        public UnitTestAsyncEnumerable(IEnumerable<T> enumerable)

            : base(enumerable)

        { }


        public UnitTestAsyncEnumerable(Expression expression)

            : base(expression)

        { }


        public IAsyncEnumerator<T> GetEnumerator()

        {

            return new UnitTestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());

        }


        IQueryProvider IQueryable.Provider

        {

            get { return new UnitTestAsyncQueryProvider<T>(this); }

        }

    }

}

UnitTestAsyncEnumerator.cs

using System.Collections.Generic;

using System.Threading;

using System.Threading.Tasks;


namespace MoqEFCoreExtension

{

    /// <summary>

    /// 定义关现IAsyncEnumerator<T>类型

    /// </summary>

    /// <typeparam name="T"></typeparam>

    class UnitTestAsyncEnumerator<T> : IAsyncEnumerator<T>

    {

        private readonly IEnumerator<T> _inner;


        public UnitTestAsyncEnumerator(IEnumerator<T> inner)

        {

            _inner = inner;

        }


        public void Dispose()

        {

            _inner.Dispose();

        }


        public T Current

        {

            get

            {

                return _inner.Current;

            }

        }


        public Task<bool> MoveNext(CancellationToken cancellationToken)

        {

            return Task.FromResult(_inner.MoveNext());

        }

    }

}

UnitTestAsyncQueryProvider.cs

using Microsoft.EntityFrameworkCore.Query.Internal;

using System.Collections.Generic;

using System.Linq;

using System.Linq.Expressions;

using System.Threading;

using System.Threading.Tasks;


namespace MoqEFCoreExtension

{

    /// <summary>

    /// 实现IQueryProvider接口

    /// </summary>

    /// <typeparam name="TEntity"></typeparam>

    class UnitTestAsyncQueryProvider<TEntity> : IAsyncQueryProvider

    {

        private readonly IQueryProvider _inner;


        internal UnitTestAsyncQueryProvider(IQueryProvider inner)

        {

            _inner = inner;

        }


        public IQueryable CreateQuery(Expression expression)

        {

            return new UnitTestAsyncEnumerable<TEntity>(expression);

        }


        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)

        {

            return new UnitTestAsyncEnumerable<TElement>(expression);

        }


        public object Execute(Expression expression)

        {

            return _inner.Execute(expression);

        }


        public TResult Execute<TResult>(Expression expression)

        {

            return _inner.Execute<TResult>(expression);

        }


        public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)

        {

            return new UnitTestAsyncEnumerable<TResult>(expression);

        }


        public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)

        {

            return Task.FromResult(Execute<TResult>(expression));

        }

    }

}

扩展方法类EFSetupData.cs

using Microsoft.EntityFrameworkCore;

using Moq;

using System.Collections.Generic;

using System.Linq;



namespace MoqEFCoreExtension

{

    /// <summary>

    /// Mock Entity Framework Core中DbContext,加载List<T>或T[]到DbSet<T>

    /// </summary>

    public static class EFSetupData

    {

        /// <summary>

        /// 加载List<T>到DbSet

        /// </summary>

        /// <typeparam name="T">实体类型</typeparam>

        /// <param name="mockSet">Mock<DbSet>对象</param>

        /// <param name="list">实体列表</param>

        /// <returns></returns>

        public static Mock<DbSet<T>> SetupList<T>(this Mock<DbSet<T>> mockSet, List<T> list) where T : class

        {

            return mockSet.SetupArray(list.ToArray());

        }

        /// <summary>

        /// 加载数据到DbSet

        /// </summary>

        /// <typeparam name="T">实体类型</typeparam>

        /// <param name="mockSet">Mock<DbSet>对象</param>

        /// <param name="array">实体数组</param>

        /// <returns></returns>

        public static Mock<DbSet<T>> SetupArray<T>(this Mock<DbSet<T>> mockSet, params T[] array) where T : class

        {

            var queryable = array.AsQueryable();

            mockSet.As<IAsyncEnumerable<T>>().Setup(m => m.GetEnumerator()).Returns(new UnitTestAsyncEnumerator<T>(queryable.GetEnumerator()));

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

            mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);

            mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);

            mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());

            return mockSet;

        }

    }

}

var answerSet = new Mock<DbSet<Answers>>().SetupList(list);替换扩展方法,以至于在answerRepository.ModifyAnswer(answer)中调用SingleOrDefault时,操作的是具有两个answers的list,而非DbSet。

源码和Sample:https://github.com/axzxs2001/MoqEFCoreExtension

同时,我把这个功能封闭成了一个Nuget包,参见:https://www.nuget.org/packages/MoqEFCoreExtension/

最后上一个图压压惊:

原文地址:http://www.cnblogs.com/axzxs2001/p/7777311.html


NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值