zoukankan      html  css  js  c++  java
  • 在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

     1 using System.Collections.Generic;
     2 using System.Linq;
     3 using System.Linq.Expressions;
     4 
     5 namespace MoqEFCoreExtension
     6 {
     7     /// <summary>
     8     /// 自定义实现EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>类型
     9     /// </summary>
    10     /// <typeparam name="T"></typeparam>
    11     class UnitTestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
    12     {
    13         public UnitTestAsyncEnumerable(IEnumerable<T> enumerable)
    14             : base(enumerable)
    15         { }
    16 
    17         public UnitTestAsyncEnumerable(Expression expression)
    18             : base(expression)
    19         { }
    20 
    21         public IAsyncEnumerator<T> GetEnumerator()
    22         {
    23             return new UnitTestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
    24         }
    25 
    26         IQueryProvider IQueryable.Provider
    27         {
    28             get { return new UnitTestAsyncQueryProvider<T>(this); }
    29         }
    30     }
    31 }

    UnitTestAsyncEnumerator.cs

     1 using System.Collections.Generic;
     2 using System.Threading;
     3 using System.Threading.Tasks;
     4 
     5 namespace MoqEFCoreExtension
     6 {
     7     /// <summary>
     8     /// 定义关现IAsyncEnumerator<T>类型
     9     /// </summary>
    10     /// <typeparam name="T"></typeparam>
    11     class UnitTestAsyncEnumerator<T> : IAsyncEnumerator<T>
    12     {
    13         private readonly IEnumerator<T> _inner;
    14 
    15         public UnitTestAsyncEnumerator(IEnumerator<T> inner)
    16         {
    17             _inner = inner;
    18         }
    19 
    20         public void Dispose()
    21         {
    22             _inner.Dispose();
    23         }
    24 
    25         public T Current
    26         {
    27             get
    28             {
    29                 return _inner.Current;
    30             }
    31         }
    32 
    33         public Task<bool> MoveNext(CancellationToken cancellationToken)
    34         {
    35             return Task.FromResult(_inner.MoveNext());
    36         }
    37     }
    38 }

    UnitTestAsyncQueryProvider.cs

     1 using Microsoft.EntityFrameworkCore.Query.Internal;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Linq.Expressions;
     5 using System.Threading;
     6 using System.Threading.Tasks;
     7 
     8 namespace MoqEFCoreExtension
     9 {
    10     /// <summary>
    11     /// 实现IQueryProvider接口
    12     /// </summary>
    13     /// <typeparam name="TEntity"></typeparam>
    14     class UnitTestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
    15     {
    16         private readonly IQueryProvider _inner;
    17 
    18         internal UnitTestAsyncQueryProvider(IQueryProvider inner)
    19         {
    20             _inner = inner;
    21         }
    22 
    23         public IQueryable CreateQuery(Expression expression)
    24         {
    25             return new UnitTestAsyncEnumerable<TEntity>(expression);
    26         }
    27 
    28         public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    29         {
    30             return new UnitTestAsyncEnumerable<TElement>(expression);
    31         }
    32 
    33         public object Execute(Expression expression)
    34         {
    35             return _inner.Execute(expression);
    36         }
    37 
    38         public TResult Execute<TResult>(Expression expression)
    39         {
    40             return _inner.Execute<TResult>(expression);
    41         }
    42 
    43         public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
    44         {
    45             return new UnitTestAsyncEnumerable<TResult>(expression);
    46         }
    47 
    48         public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    49         {
    50             return Task.FromResult(Execute<TResult>(expression));
    51         }
    52     }
    53 }

    扩展方法类EFSetupData.cs

     1 using Microsoft.EntityFrameworkCore;
     2 using Moq;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 
     6 
     7 namespace MoqEFCoreExtension
     8 {
     9     /// <summary>
    10     /// Mock Entity Framework Core中DbContext,加载List<T>或T[]到DbSet<T>
    11     /// </summary>
    12     public static class EFSetupData
    13     {
    14         /// <summary>
    15         /// 加载List<T>到DbSet
    16         /// </summary>
    17         /// <typeparam name="T">实体类型</typeparam>
    18         /// <param name="mockSet">Mock<DbSet>对象</param>
    19         /// <param name="list">实体列表</param>
    20         /// <returns></returns>
    21         public static Mock<DbSet<T>> SetupList<T>(this Mock<DbSet<T>> mockSet, List<T> list) where T : class
    22         {
    23             return mockSet.SetupArray(list.ToArray());
    24         }
    25         /// <summary>
    26         /// 加载数据到DbSet
    27         /// </summary>
    28         /// <typeparam name="T">实体类型</typeparam>
    29         /// <param name="mockSet">Mock<DbSet>对象</param>
    30         /// <param name="array">实体数组</param>
    31         /// <returns></returns>
    32         public static Mock<DbSet<T>> SetupArray<T>(this Mock<DbSet<T>> mockSet, params T[] array) where T : class
    33         {
    34             var queryable = array.AsQueryable();
    35             mockSet.As<IAsyncEnumerable<T>>().Setup(m => m.GetEnumerator()).Returns(new UnitTestAsyncEnumerator<T>(queryable.GetEnumerator()));
    36             mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new UnitTestAsyncQueryProvider<T>(queryable.Provider));
    37             mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
    38             mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
    39             mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
    40             return mockSet;
    41         }
    42     }
    43 }

      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/

    最后上一个图压压惊:

     

  • 相关阅读:
    企业生产经营相关英文及缩写之(9)称号/部门/公司
    企业生产经营相关英文及缩写之(2)生产/货仓
    企业生产经营相关英文及缩写之(6)BOM 通用缩写
    企业生产经营相关英文及缩写之(3)工程/工序(制程)
    企业生产经营相关英文及缩写之(11)Genenic 普通书写
    企业生产经营相关英文及缩写之(5)营业/采购
    企业生产经营相关英文及缩写之(7)Shipping 装运
    企业生产经营相关英文及缩写之(4)质量/体系
    软考总结
    IIS是如何处理ASP.NET请求的
  • 原文地址:https://www.cnblogs.com/axzxs2001/p/7777311.html
Copyright © 2011-2022 走看看