单元测试中使用Moq对EF的DbSet进行mock
2016-02-17 11:31
651 查看
刚用上Moq,就用它解决了一个IUnitOfWork的mock问题,在这篇博文中记录一下。
这里用到了Entity Framework中System.Data.Entity命名空间下的ToListAsync()扩展方法。
Repository层BlogCategoryRepository的实现代码如下:
这里在BlogCategoryRepository的构造函数中通过IUnitOfWork接口获取BlogCategory的数据集。
在单元测试中一开始是这样用Moq对IUnitOfWork接口进行mock的——让IUnitOfWork.Set()方法直接返回IQueryable类型的BlogCategory集合,代码如下:
出现这个错误是由于在BlogCategoryService中用到了EF的ToListAsync()扩展方法,使用这个扩展方法需要实现IDbAsyncEnumerable相关接口,而通过List转换过来的IQueryable并没有实现这个接口。要解决这个问题,我们需要使用实现IDbAsyncEnumerable相关接口的集合类型,而EF中已经天然内置了这样的集合类型,它就是DbSet。只要能mock出DbSet,问题就迎刃而解。
mock之前需要实现这三个接口:IDbAsyncEnumerator,IDbAsyncEnumerable,IDbAsyncQueryProvider 。
1)TestDbAsyncEnumerator 实现 IDbAsyncEnumerator
2)TestDbAsyncEnumerable 实现 IDbAsyncEnumerable
3)TestDbAsyncQueryProvider 实现 IDbAsyncQueryProvider
然后将之前的mock代码:
改为下面的代码:
这样成功mock出DbSet之后,单元测试成功通过,问题就解决了。
开发场景
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).
相关文章推荐
- bzoj3630 镜面通道
- mac下xampp如何设置成本地与局域网均可访问
- PowerDesigner中NAME和COMMENT的互相转换,需要执行语句
- 了解文本是属于元素还是容器
- 深入分析JavaWeb 26 -- MySQL 学习笔记
- Sublime Text 3 build 3103 license 四个供研究使用
- 大数据课程体系-学习笔记-第一阶段-Java IDE
- 随笔——runnable勘误以及其他
- 迭代器
- 视图置顶 导航栏遮挡
- 不保存批量生成二维码并压缩zip下载
- 文本垂直居中的N种方法 单行/多行文字(未知高度/固定高度)
- 请求内容
- hbase入库
- JAXB: XML 与 Java之间的映射 (OXM)
- jQuery simplePage+AJAX plus分页插件用法实例
- TDD入门demo
- HDU 1678 Shopaholic(优先队列 + 排序 + 英文-我想哭)
- TDD入门demo
- jQuery刷新当前页