Mock 框架 Moq 的使用

Mock 框架 Moq 的使用

Intro

Moq 是 .NET 中一个很流行的 Mock 框架,使用 Mock 框架我们可以只针对我们关注的代码进行测试,对于依赖项使用 Mock 对象配置预期的依赖服务的行为。

Moq 是基于 Castle 的动态代理来实现的,基于动态代理技术动态生成满足指定行为的类型

在一个项目里, 我们经常需要把某一部分程序独立出来以便我们可以对这部分进行测试. 这就要求我们不要考虑项目其余部分的复杂性, 我们只想关注需要被测试的那部分. 这里就需要用到模拟(Mock)技术.

因为, 请仔细看. 我们想要隔离测试的这部分代码对外部有一个或者多个依赖. 所以编写测试代码的时候, 我们需要提供这些依赖. 而针对隔离测试, 并不应该使用生产时用的依赖项, 所以我们使用模拟版本的依赖项, 这些模拟版依赖项只能用于测试时, 它们会使隔离更加容易.

img

绿色的是需要被测试的类,黄色Mock的依赖项

——引用自杨旭大佬的博文

Prepare

首先我们需要先准备一下用于测试的类和接口,下面的示例都是基于下面定义的类和方法来做的

public interface IUserIdProvider
{string GetUserId();
}
public class TestModel
{public int Id { get; set; }
}
public interface IRepository
{int Version { get; set; }int GetCount();Task<int> GetCountAsync();TestModel GetById(int id);List<TestModel> GetList();TResult GetResult<TResult>(string sql);int GetNum<T>();bool Delete(int id);
}public class TestService
{private readonly IRepository _repository;public TestService(IRepository repository){_repository = repository;}public int Version{get => _repository.Version;set => _repository.Version = value;}public List<TestModel> GetList() => _repository.GetList();public TResult GetResult<TResult>(string sql) => _repository.GetResult<TResult>(sql);public int GetResult(string sql) => _repository.GetResult<int>(sql);public int GetNum<T>() => _repository.GetNum<T>();public int GetCount() => _repository.GetCount();public Task<int> GetCountAsync() => _repository.GetCountAsync();public TestModel GetById(int id) => _repository.GetById(id);public bool Delete(TestModel model) => _repository.Delete(model.Id);
}

我们要测试的类型就是类似 TestService 这样的,而 IRepositoy<TestModel>IUserIdProvider 是属于外部依赖

Mock Method

Get Started

通常我们使用 Moq 最常用的可能就是 Mock 一个方法了,最简单的一个示例如下:

[Fact]
public void BasicTest()
{var userIdProviderMock = new Mock<IUserIdProvider>();userIdProviderMock.Setup(x => x.GetUserId()).Returns("mock");Assert.Equal("mock", userIdProviderMock.Object.GetUserId());
}

Match Arguments

通常我们的方法很多是带有参数的,在使用 Moq 的时候我们可以通过设置参数匹配为不同的参数返回不同的结果,来看下面的这个例子:

[Fact]
public void MethodParameterMatch()
{var repositoryMock = new Mock<IRepository>();repositoryMock.Setup(x => x.Delete(It.IsAny<int>())).Returns(true);repositoryMock.Setup(x => x.GetById(It.Is<int>(_ => _ > 0))).Returns((int id) => new TestModel(){Id = id});var service = new TestService(repositoryMock.Object);var deleted = service.Delete(new TestModel());Assert.True(deleted);var result = service.GetById(1);Assert.NotNull(result);Assert.Equal(1, result.Id);result = service.GetById(-1);Assert.Null(result);repositoryMock.Setup(x => x.GetById(It.Is<int>(_ => _ <= 0))).Returns(() => new TestModel(){Id = -1});result = service.GetById(0);Assert.NotNull(result);Assert.Equal(-1, result.Id);
}

通过 It.IsAny<T> 来表示匹配这个类型的所有值,通过 It.Is<T>(Expression<Func<bool>>) 来设置一个表达式来断言这个类型的值

通过上面的例子,我们可以看的出来,设置返回值的时候,可以直接设置一个固定的返回值,也可以设置一个委托来返回一个值,也可以根据方法的参数来动态配置返回结果

Async Method

现在很多地方都是在用异步方法,Moq 设置异步方法有三种方式,一起来看一下示例:

[Fact]
public async Task AsyncMethod()
{var repositoryMock = new Mock<IRepository>();// Task.FromResultrepositoryMock.Setup(x => x.GetCountAsync()).Returns(Task.FromResult(10));// ReturnAsyncrepositoryMock.Setup(x => x.GetCountAsync()).ReturnsAsync(10);// Mock Result, start from 4.16repositoryMock.Setup(x => x.GetCountAsync().Result).Returns(10);var service = new TestService(repositoryMock.Object);var result = await service.GetCountAsync();Assert.True(result > 0);
}

还有一个方式也可以,但是不推荐,编译器也会给出一个警告,就是下面这样

repositoryMock.Setup(x => x.GetCountAsync()).Returns(async () => 10);

Generic Type

有些方法会是泛型方法,对于泛型方法,我们来看下面的示例:

[Fact]
public void GenericType()
{var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);repositoryMock.Setup(x => x.GetResult<int>(It.IsAny<string>())).Returns(1);Assert.Equal(1, service.GetResult(""));repositoryMock.Setup(x => x.GetResult<string>(It.IsAny<string>())).Returns("test");Assert.Equal("test", service.GetResult<string>(""));
}[Fact]
public void GenericTypeMatch()
{var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);repositoryMock.Setup(m => m.GetNum<It.IsAnyType>()).Returns(-1);repositoryMock.Setup(m => m.GetNum<It.IsSubtype<TestModel>>()).Returns(0);repositoryMock.Setup(m => m.GetNum<string>()).Returns(1);repositoryMock.Setup(m => m.GetNum<int>()).Returns(2);Assert.Equal(0, service.GetNum<TestModel>());Assert.Equal(1, service.GetNum<string>());Assert.Equal(2, service.GetNum<int>());Assert.Equal(-1, service.GetNum<byte>());
}

如果要 Mock 指定类型的数据,可以直接指定泛型类型,如上面的第一个测试用例,如果要不同类型设置不同的结果一种是直接设置类型,如果要指定某个类型或者某个类型的子类,可以用 It.IsSubtype<T>,如果要指定值类型可以用 It.IsValueType,如果要匹配所有类型则可以用 It.IsAnyType

Callback

我们在设置 Mock 行为的时候可以设置 callback 来模拟方法执行时的逻辑,来看一下下面的示例:

[Fact]
public void Callback()
{var deletedIds = new List<int>();var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);repositoryMock.Setup(x => x.Delete(It.IsAny<int>())).Callback((int id) =>{deletedIds.Add(id);}).Returns(true);for (var i = 0; i < 10; i++){service.Delete(new TestModel() { Id = i });}Assert.Equal(10, deletedIds.Count);for (var i = 0; i < 10; i++){Assert.Equal(i, deletedIds[i]);}
}

Verification

有时候我们会验证某个方法是否执行,并不需要关注是否方法的返回值,这时我们可以使用 Verification 验证某个方法是否被调用,示例如下:

[Fact]
public void Verification()
{var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);service.Delete(new TestModel(){Id = 1});repositoryMock.Verify(x => x.Delete(1));repositoryMock.Verify(x => x.Version, Times.Never());Assert.Throws<MockException>(() => repositoryMock.Verify(x => x.Delete(2)));
}

如果方法没有被调用,就会引发一个 MockException 异常:

verification failed

Verification 也可以指定方法触发的次数,比如:repositoryMock.Verify(x => x.Version, Times.Never);,默认是 Times.AtLeastOnce,可以指定具体次数 Times.Exactly(1) 或者指定一个范围 Times.Between(1,2, Range.Inclusive),Moq 也提供了一些比较方便的方法,比如Times.Never()/Times.Once()/Times.AtLeaseOnce()/Times.AtMostOnce()/Times.AtLease(2)/Times.AtMost(2)

Mock Property

Moq 也可以 mock 属性,property 的本质是方法加一个字段,所以也可以用 Mock 方法的方式来 Mock 属性,只是使用 Mock 方法的方式进行 Mock 属性的话,后续修改属性值就不会引起属性值的变化了,如果修改属性,则要使用 SetupProperty 的方式来 Mock 属性,具体可以参考下面的这个示例:

[Fact]
public void Property()
{var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);repositoryMock.Setup(x => x.Version).Returns(1);Assert.Equal(1, service.Version);service.Version = 2;Assert.Equal(1, service.Version);
}[Fact]
public void PropertyTracking()
{var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);repositoryMock.SetupProperty(x => x.Version, 1);Assert.Equal(1, service.Version);service.Version = 2;Assert.Equal(2, service.Version);
}

Sequence

我们可以通过 Sequence 来指定一个方法执行多次返回不同结果的效果,看一下示例就明白了:

[Fact]
public void Sequence()
{var repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);repositoryMock.SetupSequence(x => x.GetCount()).Returns(1).Returns(2).Returns(3).Throws(new InvalidOperationException());Assert.Equal(1, service.GetCount());Assert.Equal(2, service.GetCount());Assert.Equal(3, service.GetCount());Assert.Throws<InvalidOperationException>(() => service.GetCount());
}

第一次调用返回值是1,第二次是2,第三次是3,第四次是抛了一个 InvalidOperationException

LINQ to Mocks

我们可以通过 Mock.Of 来实现类似 LINQ 的方式,创建一个 mock 对象实例,指定类型的实例,如果对象比较深,要 mock 的对象比较多使用这种方式可能会一定程度上简化自己的代码,来看使用示例:

[Fact]
public void MockLinq()
{var services = Mock.Of<IServiceProvider>(sp =>sp.GetService(typeof(IRepository)) == Mock.Of<IRepository>(r => r.Version == 1) &&sp.GetService(typeof(IUserIdProvider)) == Mock.Of<IUserIdProvider>(a => a.GetUserId() == "test"));Assert.Equal(1, services.ResolveService<IRepository>().Version);Assert.Equal("test", services.ResolveService<IUserIdProvider>().GetUserId());
}

Mock Behavior

默认的 Mock Behavior 是 Loose,默认没有设置预期行为的时候不会抛异常,会返回方法返回值类型的默认值或者空数组或者空枚举,

在声明 Mock 对象的时候可以指定 Behavior 为 Strict,这样就是一个**"真正"**的 mock 对象,没有设置预期行为的时候就会抛出异常,示例如下:

[Fact]
public void MockBehaviorTest()
{// Make mock behave like a "true Mock",// raising exceptions for anything that doesn't have a corresponding expectation: in Moq slang a "Strict" mock;// default behavior is "Loose" mock,// which never throws and returns default values or empty arrays, enumerable, etcvar repositoryMock = new Mock<IRepository>();var service = new TestService(repositoryMock.Object);Assert.Equal(0, service.GetCount());Assert.Null(service.GetList());var arrayResult = repositoryMock.Object.GetArray();Assert.NotNull(arrayResult);Assert.Empty(arrayResult);repositoryMock = new Mock<IRepository>(MockBehavior.Strict);Assert.Throws<MockException>(() => new TestService(repositoryMock.Object).GetCount());
}

使用 Strict 模式不设置预期行为的时候就会报异常,异常信息类似下面这样:

strict exception

More

Moq 还有一些别的用法,还支持事件的操作,还有 Protected 成员的 Mock,还有一些高级的用法,自定义 Default 行为等,感觉我们平时可能并不太常用,所以上面并没有加以介绍,有需要用的可以参考 Moq 的文档

上述测试代码可以在 Github 获取 https://github.com/WeihanLi/SamplesInPractice/blob/master/XunitSample/MoqTest.cs

References

  • https://github.com/moq/moq4/wiki/Quickstart

  • https://github.com/moq/moq4

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/XunitSample/MoqTest.cs

  • https://www.cnblogs.com/tylerzhou/p/11410337.html

  • https://www.cnblogs.com/cgzl/p/9304567.html

  • https://www.cnblogs.com/haogj/archive/2011/07/22/2113496.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

凝思系统改时间_国产操作系统往事:四十年激变,终再起风云

在这一轮技术浪潮中&#xff0c;无论是底层的开发生态&#xff0c;算力的硬件基础&#xff0c;还是五花八门的商业化落地场景&#xff0c;中国企业都有着毋庸置疑的话语输出能力。而另一个全民皆知、中国ICT产业的长期阵痛——国产操作系统&#xff0c;也在2019世界人工智能大会…

程序员必知的 Python 陷阱与缺陷列表

我个人对陷阱的定义是这样的&#xff1a;代码看起来可以工作&#xff0c;但不是以你“想当然“”的方式。如果一段代码直接出错&#xff0c;抛出了异常&#xff0c;我不认为这是陷阱。比如&#xff0c;Python程序员应该都遇到过的“UnboundLocalError”, 示例&#xff1a;>&…

楚乔传手游 服务器维护,楚乔传手游网络异常进不去游戏怎么办?楚乔传网络异常解决方法...

随着楚乔传的大热&#xff0c;手游楚乔传也应运而生&#xff0c;上线后的楚乔传手游版虽然好玩但也出现了许多的BUG和问题&#xff0c;最近有小伙伴说楚乔传手游下载后&#xff0c;打不开一直显示网络异常&#xff0c;但是网都好的&#xff0c;那么楚乔传手游网络异常怎么办?楚…

聊一聊和Nacos 2.0.0对接那些事

前言 nacos 2.0.0 已经发布了 alpha1, alpha2 和 beta 三个版本了&#xff0c;部分测试报告也已经出来了。Nacos2.0.0-ALPHA2 服务发现性能测试报告Nacos 2.0.0-ALPHA2 配置性能测试报告还是比较值得期待的。前段时间也一直在完善 nacos-sdk-csharp 这个项目。主要就是对接 Nac…

家用简单电线路图_家庭配电箱接线图解 家用配电箱安装方法

家居装修对水电的关注不可或缺&#xff0c;家庭电路的铺设更是至关重要的环节&#xff0c;其中家庭配电箱安装涉及到家居用电的安全问题。因此&#xff0c;小编特地对家庭配电箱安装的知识&#xff0c;以及家庭配电箱接线图作出相关整理&#xff0c;帮助大家对家庭配电箱有一个…

决策树算法及实现

在计算机科学中&#xff0c;树是一种很重要的数据结构&#xff0c;比如我们最为熟悉的二叉查找树&#xff08;Binary Search Tree&#xff09;&#xff0c;红黑树&#xff08;Red-Black Tree&#xff09;等&#xff0c;通过引入树这种数据结构&#xff0c;我们可以很快地缩小问…

ElasticSearch+NLog实现.net core分布式日志管理

概述Elasticsearch可广泛应用于日志分析、全文检索、结构化数据分析等多种场景&#xff0c;大幅度降低维护多套专用系统的成本&#xff0c;在开源社区非常受欢迎。在系统中&#xff0c;如果将日志作为文件输出&#xff0c;查看系统日志将非常不便&#xff1b;如果将日志保存到数…

双路服务器只显示一半内存,双路服务器只显示一半内存

双路服务器只显示一半内存 内容精选换一换北京时间1月3日&#xff0c;Intel处理器芯片被曝出存在严重的Meltdown和Spectre安全漏洞&#xff0c;漏洞详情如下&#xff1a;漏洞名称&#xff1a;Intel处理器存在严重芯片级漏洞漏洞编号&#xff1a;CVE-2017-5753、CVE-2017-5715、…

不想再被鄙视?那就看进来! 一文搞懂 Python 2 字符编码

程序员都自视清高&#xff0c;觉得自己是创造者&#xff0c;经常鄙视不太懂技术的产品或者QA。可悲的是&#xff0c;程序员之间也相互鄙视&#xff0c;程序员的鄙视链流传甚广&#xff0c;作为一个Python程序员&#xff0c;自然最关心的是下面这幅图啦我们项目组一值使用Python…

mysql的外键_mysql如何查看外键

展开全部查看mysql外键方式主要是通过第三方工具或者62616964757a686964616fe4b893e5b19e31333431373233是sql语句&#xff0c;主要有以下三种方式1、使用Navicateformysql&#xff0c;打开数据库、查看数据库表、查看设计表、选择外键选项卡&#xff0c;就可以查看外键2、使用…

循环递归,相互结合,释放数据的价值

随着经济的发展&#xff0c;目前各行各业已经积累了海量的数据&#xff0c;并且还在持续增长&#xff0c;可是这些数据非常杂乱还占空间&#xff0c;因此&#xff0c;如何有效利用它们&#xff0c;达到资源不浪费也就成为了相关工作者的首要思考问题。此时&#xff0c;数据分析…

黄聪:Microsoft Enterprise Library 5.0 系列教程(四) Logging Application Block

企业库日志应用程序模块工作原理图: 从上图我们可以看清楚企业库日志应用程序模块的工作原理,其中LogFilter,Trace Source,Trace Listener,Log Formatter的信息都可以在Category配置文件中反映出来,通过配置文件,调用LogWriter类的Writer方法,就可以将包含日志信息的LogEntry实…

Webapi测试工具WebBenchmark v1.3发布

这个版本更新的内容是统一使用BeetleX的Web SPA 插件作为服务的基础支持功能方便后期功能扩展&#xff0c;修复线程池配置太小引起的测试问题&#xff0c;修复统计显示的BUG。安装工具提供win64和linux64两个版本&#xff0c;可以根据自己需要下载对应系统的运行版本。地址是:h…

详解哈希表的查找

哈希表和哈希函数在记录的存储位置和它的关键字之间是建立一个确定的对应关系&#xff08;映射函数&#xff09;&#xff0c;使每个关键字和一个存储位置能唯一对应。这个映射函数称为哈希函数&#xff0c;根据这个原则建立的表称为哈希表(Hash Table)&#xff0c;也叫散列表。…

微软腾讯京东都在高薪招.NET Core,你准备好了吗!

金三银四跳槽季&#xff0c;古人诚不我欺&#xff0c;2年没更新简历了&#xff0c;还接到好几个电话邀约&#xff0c;打过交道的几个猎头妹子更是殷勤的频繁打招呼。认真了解一下才知道&#xff0c;今年的招聘真的很热&#xff0c;.NET招聘真的很热。头部互联网企业像微软苏州、…

.NET Core dump 分析

服务 CPU 或 内存偶尔飙高是部署环境中经常遇到的问题&#xff0c;一般会采用记录日志的方式来诊断&#xff0c;不过有些情况靠日志可能并不能分析出个所以然&#xff0c;面对实在无头绪的问题也只能暂时使用重启大法先恢复。为了尽可能精准的定位问题&#xff0c;掌握通过 dum…

外国人最常说的100个“中国词”出炉,第一个你绝对想不到…

近几年&#xff0c;“汉语热”在全球兴起&#xff0c;外国人说的念的中国词儿变多了&#xff01;那外国人最常说的、最热的“中国词”到底是啥呢&#xff1f;2月17日&#xff0c;中国外文局首次发布《中国话语海外认知度调研报告》。报告显示&#xff0c;近两年中国话语以汉语拼…

python 什么可以作为变量名_为什么强烈禁止开发人员使用isSuccess作为变量名

在日常开发中&#xff0c;我们会经常要在类中定义布尔类型的变量&#xff0c;比如在给外部系统提供一个RPC接口的时候&#xff0c;我们一般会定义一个字段表示本次请求是否成功的。关于这个”本次请求是否成功”的字段的定义&#xff0c;其实是有很多种讲究和坑的&#xff0c;稍…

自建Git服务器系列——Gitea(Gogs的孪生兄弟)

概述该项目的目标是提供一种最简单&#xff0c;最快&#xff0c;最轻松的方式来建立自托管的Git服务。使用Go&#xff0c;可以在Go支持的所有平台上进行独立的二进制分发 &#xff0c;包括x86&#xff0c;amd64&#xff0c;ARM和PowerPC体系结构上的Linux&#xff0c;macOS和Wi…

干货|吴恩达Coursera课程教你学习神经网络!

吴恩达Coursera机器学习课程系列笔记讲解课程笔记|吴恩达Coursera机器学习 Week1 笔记-机器学习基础干货|机器学习零基础&#xff1f;不要怕&#xff0c;吴恩达机器学习课程笔记2-多元线性回归干货|机器学习零基础&#xff1f;不要怕&#xff0c;吴恩达课程笔记第三周&#xff…