刚用上Moq,就用它解决了一个IUnitOfWork的mock问题,在这篇博文中记录一下。

开发场景

Application服务层BlogCategoryService的实现代码如下:

public class BlogCategoryService : IBlogCategoryService
{
private IBlogCategoryRepository _blogCategoryRepository; public BlogCategoryServiceImp(IBlogCategoryRepository blogCategoryRepository)
{
_blogCategoryRepository = blogCategoryRepository;
} public async Task<IList<BlogCategory>> GetCategoriesAsync(int blogId)
{
return await _blogCategoryRepository.GetCategories(blogId).ToListAsync();
}
}

这里用到了Entity Framework中System.Data.Entity命名空间下的ToListAsync()扩展方法。

Repository层BlogCategoryRepository的实现代码如下:

public class BlogCategoryRepository : IBlogCategoryRepository
{
private IQueryable<BlogCategory> _categories; public BlogCategoryRepository(IUnitOfWork unitOfWork)
{
_categories = unitOfWork.Set<BlogCategory>();
} public IQueryable<BlogCategory> GetCategories(int blogId)
{
return _categories.Where(c => c.BlogId == blogId);
}
}

这里在BlogCategoryRepository的构造函数中通过IUnitOfWork接口获取BlogCategory的数据集。

在单元测试中一开始是这样用Moq对IUnitOfWork接口进行mock的——让IUnitOfWork.Set()方法直接返回IQueryable类型的BlogCategory集合,代码如下:

[Fact]
public async Task GetCategoriesTest()
{
var blogCategories = new List<BlogCategory>()
{
new BlogCategory { BlogId = 1, Active = true, CategoryId = 1, Title = "C#" },
new BlogCategory { BlogId = 1, Active = false, CategoryId = 2, Title = "ASP.NET Core" }
}.AsQueryable(); var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork.Setup(u => u.Set<BlogCategory>()).Returns(blogCategories); _categoryService = new BlogCategoryServiceImp(new BlogCategoryRepository(mockUnitOfWork.Object)); var actual = await _categoryService.GetCategoriesAsync(1);
Assert.Equal(2, actual.Count());
actual.ToList().ForEach(c => Assert.Equal(1, c.BlogId));
}

遇到问题

运行单元测试时,却出现下面的错误:

The source IQueryable doesn't implement IDbAsyncEnumerable<BlogCategory>.
Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations.

出现这个错误是由于在BlogCategoryService中用到了EF的ToListAsync()扩展方法,使用这个扩展方法需要实现IDbAsyncEnumerable相关接口,而通过List转换过来的IQueryable并没有实现这个接口。要解决这个问题,我们需要使用实现IDbAsyncEnumerable相关接口的集合类型,而EF中已经天然内置了这样的集合类型,它就是DbSet。只要能mock出DbSet,问题就迎刃而解。

解决问题

那如何mock呢?比想象中复杂得多,幸好在msdn网站上发现了现成的mock实现代码(详见 Testing with a mocking framework ),照此就可以轻松mock。

mock之前需要实现这三个接口:IDbAsyncEnumerator,IDbAsyncEnumerable,IDbAsyncQueryProvider 。

1)TestDbAsyncEnumerator 实现 IDbAsyncEnumerator

public class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner; public TestDbAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
} public void Dispose()
{
_inner.Dispose();
} public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
} public T Current
{
get { return _inner.Current; }
} object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}

2)TestDbAsyncEnumerable 实现 IDbAsyncEnumerable

public class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ } public TestDbAsyncEnumerable(Expression expression)
: base(expression)
{ } public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
} IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
} IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}

3)TestDbAsyncQueryProvider 实现 IDbAsyncQueryProvider

public class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner; public TestDbAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
} public IQueryable CreateQuery(Expression expression)
{
return new TestDbAsyncEnumerable<TEntity>(expression);
} public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestDbAsyncEnumerable<TElement>(expression);
} public object Execute(Expression expression)
{
return _inner.Execute(expression);
} public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
} public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
} public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}

然后将之前的mock代码:

var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork.Setup(u => u.Set<BlogCategory>()).Returns(blogCategories);

改为下面的代码:

#region mockSet
var mockSet = new Mock<DbSet<BlogCategory>>();
mockSet.As<IDbAsyncEnumerable<BlogCategory>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<BlogCategory>(blogCategories.GetEnumerator())); mockSet.As<IQueryable<BlogCategory>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<BlogCategory>(blogCategories.Provider)); mockSet.As<IQueryable<BlogCategory>>().Setup(m => m.Expression).Returns(blogCategories.Expression);
mockSet.As<IQueryable<BlogCategory>>().Setup(m => m.ElementType).Returns(blogCategories.ElementType);
mockSet.As<IQueryable<BlogCategory>>().Setup(m => m.GetEnumerator()).Returns(blogCategories.GetEnumerator());
#endregion var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork.Setup(u => u.Set<BlogCategory>()).Returns(mockSet.Object);

这样成功mock出DbSet之后,单元测试成功通过,问题就解决了。

1 passed, 0 failed, 0 skipped, took 2.75 seconds (xUnit.net 1.9.2 build 1705).

单元测试中使用Moq对EF的DbSet进行mock的更多相关文章

  1. 使用Ninject+Moq在单元测试中抽象数据访问层

    一.测试方法的业务逻辑时,通常都需要从数据库读取测试数据,但是每次初始化数据库数据都很麻烦,也会影响到其它业务对数据的访问,怎样抽象数据访问层呢?就是用Moq去模拟数据访问的逻辑     二.步骤如下 ...

  2. 【MVC 4】4.MVC 基本工具(Visual Studio 的单元测试、使用Moq)

     作者:[美]Adam Freeman      来源:<精通ASP.NET MVC 4> 3.Visual Studio 的单元测试 有很多.NET单元测试包,其中很多是开源和免费的.本 ...

  3. MVC 基本工具(Visual Studio 的单元测试、使用Moq)

    3.Visual Studio 的单元测试 有很多.NET单元测试包,其中很多是开源和免费的.本文打算使用 Visual Studio 附带的内建单元测试支持,但其他一些.NET单元测试包也是可用的. ...

  4. Spring Boot 2 实践记录之 封装依赖及尽可能不创建静态方法以避免在 Service 和 Controller 的单元测试中使用 Powermock

    在前面的文章中(Spring Boot 2 实践记录之 Powermock 和 SpringBootTest)提到了使用 Powermock 结合 SpringBootTest.WebMvcTest ...

  5. ASP.NET 5 单元测试中使用依赖注入

    相关博文:<ASP.NET 5 使用 TestServer 进行单元测试> 在上一篇博文中,主要说的是,使用 TestServer 对 ASP.NET 5 WebApi 进行单元测试,依赖 ...

  6. EF的DbSet属性的Where查询,注意事项

    #1 Func<T,bool>与 Expression<Func<T,bool>>的区别 Func<T,bool>本身就是一个委托(delegate), ...

  7. 16.翻译系列:EF 6 Code -First中使用存储过程【EF 6 Code-First系列】

    原文链接:https://www.entityframeworktutorial.net/entityframework6/code-first-insert-update-delete-stored ...

  8. 单元测试中如何配置log4net

    按道理来说,单元测试中基本没有对于日志的需求,这是由于单元测试的定位来决定的. 因为单元测试的思想就是针对的都是小段代码的测试,逻辑明确,如果测试运行不通过,简单调试一下,就能很容易地排查问题.但是单 ...

  9. 如何在单元测试中测试异步函数,block回调这种

    大概有四种方法: runloop 阻塞主进程等待结果 semphaore 阻塞主进程等待结果 使用XCTestExpectation 阻塞主线程等待(我用这个,xcode自带的,为啥不用) 使用第三方 ...

随机推荐

  1. iptables转发

    需求 将流入服务器的公网IP的80端口的流量全部转发到另一个公网IP(1.2.3.4)的80端口上. 操作 iptables -P FORWARD ACCEPT iptables -t nat -A ...

  2. ---ps 命令

    ps 的命令真复杂啊! 值得注意的是选项的 -e e 这两个是不同的 -e的选项和x的意思相当 而e 的意思是改变显示栏目内容了 我个人用单字母的比较多 ps f -eo pid,uid,gid,us ...

  3. [Reship]如何回复审稿人意见

    ================================= This article came from here:http://blog.renren.com/GetEntry.do?id= ...

  4. 了解 XSS 攻击原理

    在了解 XSS 之前必须知道『网站登入(Session)』的原理 简单的说当会员成功登入后 网站会给浏览器一个『令牌』 之后只要拿着这个『令牌』到网站上 就会被视为已经登入 再来下面是 XSS 最简单 ...

  5. (引用 )自动化测试报告HTMLtestrunner

    1>下载HTMLTestRunner.py文件,地址为: http://tungwaiyip.info/software/HTMLTestRunner.html   Windows平台: 将下载 ...

  6. POJ 3180-The Cow Prom (图论-有向图强联通tarjan算法)

    题目大意:有n个牛在一块, m条单项绳子, 有m个链接关系, 问有多少个团体内部任意两头牛可以相互可达 解题思路:有向图强连通分量模版图 代码如下: #include<stdio.h> # ...

  7. Collection List Set和Map用法与区别

    labels:Collection List Set和Map用法与区别 java 散列表 集合 Collection           接 口的接口      对 象的集合   ├   List   ...

  8. log4net记录日志到数据库自定义字段

    假设数据库中有如下自定义字段:   1.根据自定义字段定义日志信息对象     public class MessageLog     {           /// <summary> ...

  9. Eclipse+Mingw+Boost 环境搭建

    一.安装CDT插件 Eclipse是用Java的swt开发的,所以需要java虚拟机才能运行,jdk的配置方法网上一大堆,这里就不细说了.安装CDT的方法简单提一下,到Help->Eclipse ...

  10. 利用fiddler模拟发送json数据的post请求

    fiddler是调试利器,有许多好用的功能,这里简单的介绍一下利用fiddler模拟发送post请求的例子 先简单介绍一下失败的例子,最后给出正确的方法