.net测试篇之Moq框架简单使用

系列目录

Moq库简介及安装 Moq简介

Moq是.net平台下的一个非常流行的模拟库,只要有一个接口它就可以动态生成一个对象,底层使用的是Castle的动态代理功能.

它的流行赖于依赖注入模式的兴起,现在越来越多的分层架构使用依赖注入的方式来解耦层与层之间的关系.最为常见的是数据层和业务逻辑层之间的依赖注入,业务逻辑层不再强依赖数据层对象,而是依赖数据层对象的接口,在IOC容器里完成依赖的配置.

这种解耦给单元测试带来了巨大的便利,使得对业务逻辑的测试可以脱离对数据层的依赖,单元测试的粒度更小,更容易排查出问题所在.

大家可能都知道,数据层的接口往往有很多方法,少则十几个,多则几十个.我们如果在单元测试的时候把接口切换为假实现,即使实现类全是空也需要大量代码,并且这些代码不可重用,一旦接口层改变不但要更改真实数据层实现还要修改这些专为测试做的假实现.这显然是不小的工作量.

幸好有Moq,它可以在编译时动态生成接口的代理对象.大大提高了代码的可维护性,同时也极大减少工作量.

除了动态创建代理外,Moq还可以进行行为测试,触发事件等.

Moq安装

Moq安装非常简单,在Nuget里面搜索moq,第一个结果便是moq框架,点击安装即可.

Moq简单使用

本示例中要使用到的代码如下

public class MyDto { public string Name { get; set; } public int Age { get; set; } } public interface IDataBaseContext<out T> where T:new() { T GetElementById(string id); IEnumerable<T> GetAll(); IEnumerable<T> GetElementsByName(string name); IEnumerable<T> GetPageElementsByName(string name, int startPage = 0, int pageSize = 20); IEnumerable<T> GetElementsByDate(DateTime? startDate, DateTime? endDate); } public class MyBll { private readonly IDataBaseContext<MyDto> _dataBaseContext; public MyBll(IDataBaseContext<MyDto> dataBaseContext) { _dataBaseContext = dataBaseContext; } public MyDto GetADto(string id) { if (string.IsNullOrWhiteSpace(id)) return null; return _dataBaseContext.GetElementById(id); } }

MyDto为业务层和数据层交互的对象,IDataBaseContext为数据层接口,MyBll为我们的业务逻辑层

我们要测试的是业务逻辑层的代码.这里业务逻辑类并没有无参构造函数,如果手动创建起来非常麻烦,里面的坑前面说过.下面看如何使用Moq来模拟一个IDataBaseContext对象

我们编写以下测试类

[Test] public void SimpleTest() { var moq = new Mock<IDataBaseContext<MyDto>>(); MyBll bll = new MyBll(moq.Object); var result = bll.GetADto(null); Assert.Null(result); }

由于bll的GetADto如果传的参数是null或者空就会返回一个null对象,因些返回的结果是Null,以上测试会通过.

这里我们首先创建了一个moq对象,它的Object属性就是我们要模拟的IDataBaseContext对象,我们在创建MyBll对象时把它作为参数传入.

Moq基本配置

我们再为MyBll添加以下方法

public IEnumerable<MyDto> GetDtos(string name) { if (string.IsNullOrWhiteSpace(name)) return null; var dtos = _dataBaseContext.GetElementsByName(name); return dtos; }

我们编写如下测试方法

[Test] public void ShouldReturn_A_Collection_Of_Dtos() { var moq = new Mock<IDataBaseContext<MyDto>>(); MyBll bll = new MyBll(moq.Object); var dtos = bll.GetDtos("sto"); }

以上测试方法调用了bll的GetDtos方法,我们知道GetDtos内部调用了数据访问接口的GetElementsByName方法,我们在调试模式下看看返回的结果是什么.

Avatar

它返回了一个空集合,实际上不管我们提供的是什么样的字符串,它都返回一个空集合,这是默认行为,因为_dataBaseContext.GetElementsByName并不知道我们的真实逻辑是什么.

这样很显然并不是总能满足我们的要求,很多时候我们在测试业务逻辑层的时候需要具体的数据,然后才能继续往下走.

比如以下方法,我们获取数据库里的所有数据,然而通过一系列逻辑进行过滤,最终返回过滤后的结果.

public IEnumerable<MyDto> GetAllDtos() { var all = _dataBaseContext.GetAll().ToList(); if (!all.Any()) return Enumerable.Empty<MyDto>(); //一系列逻辑... var filteredDtos = all.Where(a => a.Age > 20); var orderDtos = filteredDtos.OrderBy(a => a.Name); return orderDtos; }

如果是默认行为(调用模拟的接口方法,引用对象返回null,集合返回空,简单对象返回默认值),则代码很快就返回了,if下面的业务逻辑测不到了.下面我们看下如何配置接口方法的返回值

这里其实主要用到了 新建moq对象的setup方法,我们可以在setup里设置方法,属性的值.

[Test] public void ShouldReturn_A_Collection_Of_Dtos() { var moq = new Mock<IDataBaseContext<MyDto>>(); moq.Setup(a => a.GetAll()).Returns(new List<MyDto> { new MyDto{Name="baidu",Age=15}, new MyDto{Name="sto",Age=32}, new MyDto{Name="zto",Age=24}, new MyDto{Name="yto",Age=12} }); MyBll bll = new MyBll(moq.Object); var dtos = bll.GetAllDtos().ToList(); dtos.Should().HaveCount(2); dtos.Select(a => a.Name).Should().BeInAscendingOrder(); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxxpw.html