Mock 框架 Moq 的使用

article/2025/10/27 19:49:43

Intro#

Moq 是 .NET 中一个很流vb.net教程行的 Mock 框架,使c#教程用 Mock 框架我python基础教程们可以只针对我java基础教程们关注的代码进行测试,对于sql教程依赖项使用 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

作者:WeihanLi


http://chatgpt.dhexx.cn/article/LRZohVd2.shtml

相关文章

mockjs入门

mockjs 1,mock.js是什么&#xff1f; mockjs是生成随机数据的一款前端工具&#xff0c;用来模拟 Ajax 请求&#xff0c;生成并返回模拟数据 2&#xff0c;为什么用mockjs&#xff1f; 当程序员做项目开发时&#xff0c;前端工程师要请求后端做好的数据时&#xff0c;有可能…

monkey简介

https://blog.csdn.net/lebang08/article/details/70858532 https://www.cnblogs.com/aland-1415/p/6949964.html https://blog.csdn.net/aisemi/article/details/55254348 一、Monkey 简介 monkey是Android SDK中自带的一个命令行工具&#xff0c;使用Java语言写成&#xf…

mokey的介绍和使用

一、monkey介绍 monkey是Android SDK提供的一个命令行工具&#xff0c;可以简单方便的发送伪随机的用户时间流&#xff0c;对Android APP做压力&#xff08;稳定性、健壮性&#xff09;测试。主要是为了测试APP是否存在无响应和崩溃的情况。 二、monkey的使用 1、前提条件&a…

APP测试— 测试工具mokey

文章目录 1 Mokey概念2 运行Monkey&#xff08;对手机进行300次无规律点击&#xff09;3 Mokey常规参数4 Monkey 事件类参数5 Monkey 约束类参数 1 Mokey概念 1&#xff09;Monkey是Android SDK提供的一个命令行工具&#xff0c;可以简单、方便的运行任何版本的Android模拟器和…

Kafka配置用户名密码访问

1 软件版本 kafka_2.12-2.4.0.tgz&#xff08;带zookeeper&#xff09; 2 kafka服务端部署 2.1 将安装包上传到服务器&#xff0c;并解压 tar zxvf kafka_2.12-2.4.0.tgz -C /datamv kafka_2.12-2.4.0 kafka2.2 修改kafka配置文件 server.properties vim /data/kafka/conf…

linux 用户名和密码的处理

1. 创建新用户和密码 # 创建用户 testuser useradd testuser# 给已创建的用户testuser设置密码 passwd testuser# 新创建的用户会在 /home 下创建一个用户目录testuser# 修改用户这个命令的相关参数 usermod --help# 删除用户testuser userdel testuser# 删除用户所在目录rm -…

用户名,密码登录

1.导入项目需要的依赖&#xff0c;分层 注意&#xff1a;如果你的数据库是5.5的版本&#xff0c;依赖要用低版本的&#xff0c;高版本不稳定&#xff0c;新增的内容不识别&#xff0c;会报各种各样奇葩的错误 2.创建实体类 它的属性要和数据库字段对应 package com.oa.entity…

实现用户输入用户名和密码登录

题目 实现用户输入用户名和密码登录&#xff0c;当用户名为admin或administrator且密码为666666时&#xff0c;显示“登录成功”&#xff0c;否则显示“登录失败”&#xff0c;登录失败时允许重复输入三次。 实例 参考程序 User1 "admin" User2 "administr…

计算机用户名和初始密码,电脑默认的用户名和密码是多少

优质回答 回答者&#xff1a;止树2018 电脑用户默认是没有密码的&#xff0c;除非你设置了&#xff0c;没有设置的前提下&#xff0c;直接按回车键就可以进系统了。 电脑默认的用户是administrator&#xff0c;如果你创建了自己的新用户名&#xff0c;那么&#xff0c;原始管理…

服务器密码以及用户名怎么修改

服务器密码以及用户名怎么修改 我是艾西&#xff0c;今天给大家说下服务器密码如何修改 windows2003系统&#xff1a; 1、右键我的电脑&#xff0c;点击“管理”&#xff1a; 2、在“本地用户和组”中打开“用户”&#xff0c;在右侧找到 Administrator 账户进行修改。 200…

电脑更改开机密码和用户名

一、电脑更改开机密码 1、快捷键CtrlAltDel出现以下界面。 2、点击“更改密码”&#xff0c;出现修改密码的界面&#xff0c;输入旧的密码&#xff0c;以及新的密码&#xff0c;确定即可。 二、电脑更改开机用户名 1、打开电脑的”控制面板“。 2、在控制面板中点击“用户帐户…

基于51单片机的呼吸灯程序编写

利用51单片机编写的呼吸灯小程序&#xff0c;实验程序内容截图分享~

六、Arduino呼吸灯的实现

实验所需材料 Arduino UNO面包板LED灯一个330Ω电阻一个 连接示意图 如图所示&#xff0c;实验中我们将LED连接到了带PWM功能的D9引脚。 可以在 Arduino IDE菜单>文件>示例>03.Analog>Fading 打开呼吸灯示例程序&#xff0c;程序如下&#xff1a; int ledPin 9…

C语言实现呼吸灯(HAL库)

1. 呼吸灯原理 呼吸灯的实现可以通过控制灯的亮度连续变化&#xff0c;当变化的频率大于24帧时&#xff0c;肉眼看上去就会逐渐变暗&#xff0c;逐渐变亮。 2. PWM控制亮度 PWM通过设置亮度在一段时间内的占空比&#xff0c;亮的百分比多&#xff0c;人眼看到的就亮&#xf…

二、15【FPGA】呼吸灯实现

前言 学习说明此文档为本人的学习笔记&#xff0c;注重实践&#xff0c;关于理论部分会给出相应的学习链接。 学习视频&#xff1a;是根据野火FPGA视频教程——第十八讲 https://www.bilibili.com/video/BV1nQ4y1Z7zN?p3 实战演练 一、设计规划 1.1 实验目标 在开发板上…

基于FPGA实践之呼吸灯(含程序)

呼吸灯是指灯光在微电脑的控制之下完成由亮到暗的逐渐变化&#xff0c;感觉好像是人在呼吸。 在单片机中我们调节PWM波的占空比可以实现一个周期内高电平占百分比&#xff0c;这个百分比固定就可以调节亮度&#xff0c;这个百分比是动态的&#xff0c;那么灯的亮度也是动态的&…

Verilog实现呼吸灯效果

呼吸灯的效果采用PWM调波的形式&#xff0c;即快速的改变每个周期的占空比&#xff08;一个周期内高电平时间占一个周期时间的比值&#xff09;来实现点亮到熄灭的效果。示意如下图 而关于整个波形图&#xff0c;用50MHz的晶振&#xff0c;从0开始计数到49则为1us。 而1ms是1u…

呼吸灯

呼吸灯 呼吸灯&#xff0c;就是控制led灯的亮度从弱变强、从强变弱的循环往复&#xff0c;从而实现像呼吸一样的效果。改变电压即可改变led灯的亮度&#xff0c;但是用代码控制led灯两端电压显然是不现实的&#xff0c;我们可以用控制脉冲宽度的方式来控制led灯点亮的时间&…

C51单片机实现呼吸灯

呼吸灯的效果是灯的亮度由暗缓慢变亮再缓慢变暗。 实现思路是改变小灯亮与暗在单位周期的占空比。如下图 具体代码如下&#xff1a; #include<reg52.h> typedef unsigned int u16; typedef unsigned char u8;sbit LEDP2^0; void delay(u16 i) {while(i--); }void ma…

Arduino程序设计(一) 流水灯+呼吸灯

LED灯程序设计 前言一、LED灯的程序设计1 —— 频闪灯二、LED灯的程序设计2 —— 流水灯三、LED灯的程序设计3 —— 呼吸灯总结参考文献 前言 本文主要介绍三种LED灯的程序设计&#xff0c;即频闪灯、流水灯和呼吸灯。本文使用的MCU芯片为ESP8266EX&#xff0c;程序编写使用Ar…