Mock 框架Moq 食用指南. ... 绿色的是需要被测试的类,黄色是Mock的依赖项 ... Moq 还有一些别的用法,还支持事件的操作,还有Protected 成员的Mock, ...
首页
新闻
博问
专区
闪存
班级
我的博客
我的园子
账号设置
简洁模式...
退出登录
注册
登录
Loading
Loveitorleaveit
Mock框架Moq的使用
Mock框架Moq食用指南
Mock框架Moq的使用
Intro
Moq是.NET中一个很流行的Mock框架,使用Mock框架我们可以只针对我们关注的代码进行测试,对于依赖项使用Mock对象配置预期的依赖服务的行为。
Moq是基于Castle的动态代理来实现的,基于动态代理技术动态生成满足指定行为的类型
在一个项目里,我们经常需要把某一部分程序独立出来以便我们可以对这部分进行测试.这就要求我们不要考虑项目其余部分的复杂性,我们只想关注需要被测试的那部分.这里就需要用到模拟(Mock)技术.
因为,请仔细看.我们想要隔离测试的这部分代码对外部有一个或者多个依赖.所以编写测试代码的时候,我们需要提供这些依赖.而针对隔离测试,并不应该使用生产时用的依赖项,所以我们使用模拟版本的依赖项,这些模拟版依赖项只能用于测试时,它们会使隔离更加容易.
绿色的是需要被测试的类,黄色是Mock的依赖项
——引用自杨旭大佬的博文
Prepare
首先我们需要先准备一下用于测试的类和接口,下面的示例都是基于下面定义的类和方法来做的
publicinterfaceIUserIdProvider
{
stringGetUserId();
}
publicclassTestModel
{
publicintId{get;set;}
}
publicinterfaceIRepository
{
intVersion{get;set;}
intGetCount();
TaskGetCountAsync();
TestModelGetById(intid);
ListGetList();
TResultGetResult(stringsql);
intGetNum();
boolDelete(intid);
}
publicclassTestService
{
privatereadonlyIRepository_repository;
publicTestService(IRepositoryrepository)
{
_repository=repository;
}
publicintVersion
{
get=>_repository.Version;
set=>_repository.Version=value;
}
publicListGetList()=>_repository.GetList();
publicTResultGetResult(stringsql)=>_repository.GetResult(sql);
publicintGetResult(stringsql)=>_repository.GetResult(sql);
publicintGetNum()=>_repository.GetNum();
publicintGetCount()=>_repository.GetCount();
publicTaskGetCountAsync()=>_repository.GetCountAsync();
publicTestModelGetById(intid)=>_repository.GetById(id);
publicboolDelete(TestModelmodel)=>_repository.Delete(model.Id);
}
我们要测试的类型就是类似TestService这样的,而IRepositoy和IUserIdProvider是属于外部依赖
MockMethod
GetStarted
通常我们使用Moq最常用的可能就是Mock一个方法了,最简单的一个示例如下:
[Fact]
publicvoidBasicTest()
{
varuserIdProviderMock=newMock();
userIdProviderMock.Setup(x=>x.GetUserId()).Returns("mock");
Assert.Equal("mock",userIdProviderMock.Object.GetUserId());
}
MatchArguments
通常我们的方法很多是带有参数的,在使用Moq的时候我们可以通过设置参数匹配为不同的参数返回不同的结果,来看下面的这个例子:
[Fact]
publicvoidMethodParameterMatch()
{
varrepositoryMock=newMock();
repositoryMock.Setup(x=>x.Delete(It.IsAny()))
.Returns(true);
repositoryMock.Setup(x=>x.GetById(It.Is(_=>_>0)))
.Returns((intid)=>newTestModel()
{
Id=id
});
varservice=newTestService(repositoryMock.Object);
vardeleted=service.Delete(newTestModel());
Assert.True(deleted);
varresult=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(_=>_<=0)))
.Returns(()=>newTestModel()
{
Id=-1
});
result=service.GetById(0);
Assert.NotNull(result);
Assert.Equal(-1,result.Id);
}
通过It.IsAny来表示匹配这个类型的所有值,通过It.Is(Expression>)来设置一个表达式来断言这个类型的值
通过上面的例子,我们可以看的出来,设置返回值的时候,可以直接设置一个固定的返回值,也可以设置一个委托来返回一个值,也可以根据方法的参数来动态配置返回结果
AsyncMethod
现在很多地方都是在用异步方法,Moq设置异步方法有三种方式,一起来看一下示例:
[Fact]
publicasyncTaskAsyncMethod()
{
varrepositoryMock=newMock();
//Task.FromResult
repositoryMock.Setup(x=>x.GetCountAsync())
.Returns(Task.FromResult(10));
//ReturnAsync
repositoryMock.Setup(x=>x.GetCountAsync())
.ReturnsAsync(10);
//MockResult,startfrom4.16
repositoryMock.Setup(x=>x.GetCountAsync().Result)
.Returns(10);
varservice=newTestService(repositoryMock.Object);
varresult=awaitservice.GetCountAsync();
Assert.True(result>0);
}
还有一个方式也可以,但是不推荐,编译器也会给出一个警告,就是下面这样
repositoryMock.Setup(x=>x.GetCountAsync()).Returns(async()=>10);
GenericType
有些方法会是泛型方法,对于泛型方法,我们来看下面的示例:
[Fact]
publicvoidGenericType()
{
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
repositoryMock.Setup(x=>x.GetResult(It.IsAny()))
.Returns(1);
Assert.Equal(1,service.GetResult(""));
repositoryMock.Setup(x=>x.GetResult(It.IsAny()))
.Returns("test");
Assert.Equal("test",service.GetResult(""));
}
[Fact]
publicvoidGenericTypeMatch()
{
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
repositoryMock.Setup(m=>m.GetNum())
.Returns(-1);
repositoryMock.Setup(m=>m.GetNum>())
.Returns(0);
repositoryMock.Setup(m=>m.GetNum())
.Returns(1);
repositoryMock.Setup(m=>m.GetNum())
.Returns(2);
Assert.Equal(0,service.GetNum());
Assert.Equal(1,service.GetNum());
Assert.Equal(2,service.GetNum());
Assert.Equal(-1,service.GetNum());
}
如果要Mock指定类型的数据,可以直接指定泛型类型,如上面的第一个测试用例,如果要不同类型设置不同的结果一种是直接设置类型,如果要指定某个类型或者某个类型的子类,可以用It.IsSubtype,如果要指定值类型可以用It.IsValueType,如果要匹配所有类型则可以用It.IsAnyType
Callback
我们在设置Mock行为的时候可以设置callback来模拟方法执行时的逻辑,来看一下下面的示例:
[Fact]
publicvoidCallback()
{
vardeletedIds=newList();
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
repositoryMock.Setup(x=>x.Delete(It.IsAny()))
.Callback((intid)=>
{
deletedIds.Add(id);
})
.Returns(true);
for(vari=0;i<10;i++)
{
service.Delete(newTestModel(){Id=i});
}
Assert.Equal(10,deletedIds.Count);
for(vari=0;i<10;i++)
{
Assert.Equal(i,deletedIds[i]);
}
}
Verification
有时候我们会验证某个方法是否执行,并不需要关注是否方法的返回值,这时我们可以使用Verification验证某个方法是否被调用,示例如下:
[Fact]
publicvoidVerification()
{
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
service.Delete(newTestModel()
{
Id=1
});
repositoryMock.Verify(x=>x.Delete(1));
repositoryMock.Verify(x=>x.Version,Times.Never());
Assert.Throws(()=>repositoryMock.Verify(x=>x.Delete(2)));
}
如果方法没有被调用,就会引发一个MockException异常:
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)
MockProperty
Moq也可以mock属性,property的本质是方法加一个字段,所以也可以用Mock方法的方式来Mock属性,只是使用Mock方法的方式进行Mock属性的话,后续修改属性值就不会引起属性值的变化了,如果修改属性,则要使用SetupProperty的方式来Mock属性,具体可以参考下面的这个示例:
[Fact]
publicvoidProperty()
{
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
repositoryMock.Setup(x=>x.Version).Returns(1);
Assert.Equal(1,service.Version);
service.Version=2;
Assert.Equal(1,service.Version);
}
[Fact]
publicvoidPropertyTracking()
{
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
repositoryMock.SetupProperty(x=>x.Version,1);
Assert.Equal(1,service.Version);
service.Version=2;
Assert.Equal(2,service.Version);
}
Sequence
我们可以通过Sequence来指定一个方法执行多次返回不同结果的效果,看一下示例就明白了:
[Fact]
publicvoidSequence()
{
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
repositoryMock.SetupSequence(x=>x.GetCount())
.Returns(1)
.Returns(2)
.Returns(3)
.Throws(newInvalidOperationException());
Assert.Equal(1,service.GetCount());
Assert.Equal(2,service.GetCount());
Assert.Equal(3,service.GetCount());
Assert.Throws(()=>service.GetCount());
}
第一次调用返回值是1,第二次是2,第三次是3,第四次是抛了一个InvalidOperationException
LINQtoMocks
我们可以通过Mock.Of来实现类似LINQ的方式,创建一个mock对象实例,指定类型的实例,如果对象比较深,要mock的对象比较多使用这种方式可能会一定程度上简化自己的代码,来看使用示例:
[Fact]
publicvoidMockLinq()
{
varservices=Mock.Of(sp=>
sp.GetService(typeof(IRepository))==Mock.Of(r=>r.Version==1)&&
sp.GetService(typeof(IUserIdProvider))==Mock.Of(a=>a.GetUserId()=="test"));
Assert.Equal(1,services.ResolveService().Version);
Assert.Equal("test",services.ResolveService().GetUserId());
}
MockBehavior
默认的MockBehavior是Loose,默认没有设置预期行为的时候不会抛异常,会返回方法返回值类型的默认值或者空数组或者空枚举,
在声明Mock对象的时候可以指定Behavior为Strict,这样就是一个"真正"的mock对象,没有设置预期行为的时候就会抛出异常,示例如下:
[Fact]
publicvoidMockBehaviorTest()
{
//Makemockbehavelikea"trueMock",
//raisingexceptionsforanythingthatdoesn'thaveacorrespondingexpectation:inMoqslanga"Strict"mock;
//defaultbehavioris"Loose"mock,
//whichneverthrowsandreturnsdefaultvaluesoremptyarrays,enumerable,etc
varrepositoryMock=newMock();
varservice=newTestService(repositoryMock.Object);
Assert.Equal(0,service.GetCount());
Assert.Null(service.GetList());
vararrayResult=repositoryMock.Object.GetArray();
Assert.NotNull(arrayResult);
Assert.Empty(arrayResult);
repositoryMock=newMock(MockBehavior.Strict);
Assert.Throws(()=>newTestService(repositoryMock.Object).GetCount());
}
使用Strict模式不设置预期行为的时候就会报异常,异常信息类似下面这样:
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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted@
2021-03-0800:06
WeihanLi
阅读(1279)
评论(1)
编辑
收藏
举报
刷新评论刷新页面返回顶部
Copyright©2022WeihanLi
Poweredby.NET6onKubernetes