Moq 是基於Castle 的動態代理來實現的,基於動態代理技術動態生成滿足指定行為... ... Moq 還有一些別的用法,還支持事件的操作,還有Protected 成員的Mock,還有一些 ...
it編輯入門教程
首頁
最新教程
HTML
CSS
JavaScript
jQuery
Vue
Bootstrap
Python3
Python2
Java
C
C++
C#
Go
SQL
首頁
最新教程
HTML
CSS
JS
C#
WEB端播放華為海康大華視頻方案
C#-5類和繼承
C#-2C#程序
C#-3深入理解類
C#-4方法
基於SqlSugar的數據庫訪問處理的封裝,支持多數據庫並使之適應於實際業務開發中
C#迭代器
C#模式匹配完全指南
Mock框架Moq的使用
分类C#
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
<>
Copyright©2021it編輯入門教程
itcode123.techAllRightsReserved.