你可能会用到的 Mock 小技巧
你可能會(huì)用到的 Mock 小技巧
Intro
最近看到阿迪分享了兩篇 Mock 相關(guān)的文章,于是想把自己遇到的一些可能對(duì)你有幫助的一些小技巧分享一下,大概總結(jié)了一下,且看下文
AsyncEnumerable
在 C# 8 中引入了異步流,AsyncEnumerable,在有些類庫(kù)中已經(jīng)引入了這一語(yǔ)法,在 StackExchange.Redis 中 HashScanAsync 的返回值就是 IAsyncEnumerable<HashEntry>
使用示例如下:
var?dic?=?new?Dictionary<string,?string>(); await?foreach?(var?entry?in?db.HashScanAsync(setName,?"*")) {dic[entry.Name]?=?entry.Value; }在 Mock 的時(shí)候,我們可以通過(guò)下面的 MockAsyncEnumerable 比較方便的指定一個(gè) IEnumerable 對(duì)象來(lái)實(shí)現(xiàn)一個(gè) IAsyncEnumerable 對(duì)象
private?class?MockAsyncEnumerable<T>?:?IAsyncEnumerable<T> {private?readonly?IEnumerable<T>?_data;public?MockAsyncEnumerable(IEnumerable<T>?data){_data?=?data;}public?IAsyncEnumerator<T>?GetAsyncEnumerator(CancellationToken?cancellationToken?=?new?CancellationToken()){return?new?MockAsyncEnumerator<T>(_data.GetEnumerator());} }private?class?MockAsyncEnumerator<T>?:?IAsyncEnumerator<T> {private?readonly?IEnumerator<T>?_enumerator;public?MockAsyncEnumerator(IEnumerator<T>?enumerator){_enumerator?=?enumerator;}public?ValueTask?DisposeAsync(){_enumerator.Dispose();return?default;}public?ValueTask<bool>?MoveNextAsync(){return?new?ValueTask<bool>(_enumerator.MoveNext());}public?T?Current?=>?_enumerator.Current; }使用示例如下:
var?entries?=?new?HashEntry[10]; databaseMock.Setup(c?=>?c.HashScanAsync(setName,?"*",?200,?0,?0,?CommandFlags.None)).Returns(new?MockAsyncEnumerable<HashEntry>(entries));HttpClient Mock
一個(gè)項(xiàng)目中經(jīng)常會(huì)遇到調(diào)用第三方的 API,如何比較方便的 Mock 一個(gè) HttpClient 的行為呢,我們可以通過(guò)自定義一個(gè) HttpHandler 來(lái)實(shí)現(xiàn)自定義響應(yīng)信息,通常我們需要根據(jù)不同的請(qǐng)求信息返回不同的響應(yīng),我們自定義了一個(gè) MockHttpHandler 來(lái)實(shí)現(xiàn)比較方便的 Mock 第三方 API 的行為,實(shí)現(xiàn)如下:
internal?class?MockHttpHandler?:?DelegatingHandler {private?readonly?Func<HttpRequestMessage,?HttpResponseMessage>?_getResponseFunc;public?MockHttpHandler(Func<HttpRequestMessage,?HttpResponseMessage>?getResponseFunc){_getResponseFunc?=?getResponseFunc;}protected?override?Task<HttpResponseMessage>?SendAsync(HttpRequestMessage?request,?CancellationToken?cancellationToken){return?Task.FromResult(_getResponseFunc(request));} }使用示例如下:
using?var?client?=new?HttpClient(new?MockHttpHandler(req?=>?new?HttpResponseMessage(HttpStatusCode.BadRequest))){BaseAddress?=?new?Uri("https://api.weihanli.xyz/")}; // using?var?httpClient?=?new?HttpClient(new?MockHttpHandler(request?=> {var?statusCode?=?request.RequestUri.AbsoluteUri.Contains("templateId=1")???HttpStatusCode.NotFound?:?(request.RequestUri.AbsoluteUri.Contains("templateId=2")???HttpStatusCode.BadRequest?:?HttpStatusCode.InternalServerError);return?new?HttpResponseMessage(statusCode){Content?=?new?StringContent(JsonConvert.SerializeObject(new{Code?=?statusCode.ToString(),Msg?=?"The?template?not?exists"}))}; }))MVC HttpContext Mock
HttpContext mock 示例:
var?services?=?new?ServiceCollection().AddScoped<CurrentUser>(sp?=>?new?CurrentUser(){UserID?=?1,UserName?=?"admin"}).BuildServiceProvider();var?mock?=?new?Mock<HttpContext>(); //?Mock?HttpContext.User mock.SetupGet(x?=>?x.User).Returns(new?ClaimsPrincipal(new?ClaimsIdentity(new?Claim[]{new?Claim(ClaimTypes.Name,?"admin"),new?Claim(ClaimTypes.Email,?"weihan.li@iherb.com"),},?JwtBearerDefaults.AuthenticationScheme))); //?Mock?RequestServices mock.Setup(x?=>?x.RequestServices).Returns(services);var?controller?=?new?CommonController(NullLogger<CommonController>.Instance) {ControllerContext?=?new?ControllerContext()?{HttpContext?=?mock.Object?} };MVC ExceptionFilter test
有時(shí)我們會(huì)在項(xiàng)目里使用到 ExceptionFilter 來(lái)捕獲 MVC 中未捕獲的異常,如果想要針對(duì)自定義的 ExceptionFilter 寫一些測(cè)試用例可以參考下面的測(cè)試用例:
[Fact] public?async?Task?ExceptionTest() {var?filters?=?new?IFilterMetadata[]{new?ResultExceptionFilter()};var?exceptionContext?=?new?ExceptionContext(new?ActionContext(){HttpContext?=?new?DefaultHttpContext(){RequestServices?=?new?ServiceCollection().AddLogging().BuildServiceProvider()},RouteData?=?new?RouteData(new?RouteValueDictionary(){{"controller",?"Test"},{"action",?"Test"},}),ActionDescriptor?=?new?ActionDescriptor(),},?filters){Exception?=?new?NotImplementedException()};var?invoker?=?new?Mock<IActionInvoker>();invoker.Setup(x?=>?x.InvokeAsync()).Callback(()?=>{new?ResultExceptionFilter().OnException(exceptionContext);}).Returns(Task.CompletedTask);await?invoker.Object.InvokeAsync();//?... }Mock Data
字符串
在我們的代碼中經(jīng)常會(huì)出現(xiàn)對(duì)輸入?yún)?shù)進(jìn)行校驗(yàn)是否為空,對(duì)于這樣的數(shù)據(jù)每次都取寫一遍就會(huì)有點(diǎn)煩,所以寫了一個(gè)自定義測(cè)試數(shù)據(jù),就是返回 null/空字符串,實(shí)現(xiàn)代碼如下:
public?class?NullOrEmptyStringDataAttribute?:?DataAttribute {public?bool?IncludeWhitespace?{?get;?set;?}public?override?IEnumerable<object[]>?GetData(MethodInfo?testMethod){yield?return?new?object[]?{?null?};yield?return?new?object[]?{?string.Empty?};if?(IncludeWhitespace){yield?return?new?object[]?{?"?"?};}} }使用示例如下:
[Theory] [NullOrEmptyStringData] public?void?Test(string?name) {Assert.True(string.IsNullOrEmpty(name)); }[Theory] [NullOrEmptyStringData(IncludeWhitespace=true)] public?void?Test1(string?name) {Assert.True(string.IsNullOrWhitespace(name)); }Number
對(duì)于 id 之類的數(shù)據(jù),通過(guò)我們需要檢查是否大于0,在寫測(cè)試的時(shí)候需要考慮小于等于 0 的情況,通常我們也可以像上面那樣做一個(gè)簡(jiǎn)單的封裝,實(shí)現(xiàn)代碼如下:
public?class?LessThanOrEqualDataAttribute?:?DataAttribute {public?int?Value?{?get;?set;?}public?override?IEnumerable<object[]>?GetData(MethodInfo?testMethod){yield?return?new?object[]?{?Value?};yield?return?new?object[]?{?Value?-?1?};} }使用實(shí)例如下:
[Theory] [LessThanOrEqualData] public?async?Task?GetCategoryIdInfo_BadRequest(int?id) {var?result?=?await?_controller.GetCategoryIdInfo(id,?null);result.AssertCode(ErrorCode.BadRequest); }More
上面是一些我寫測(cè)試用例的時(shí)候可能會(huì)用到的一些幫助類或 Mock 方法,希望能對(duì)你有所幫助~
你在寫測(cè)試用例的過(guò)程中還有哪些覺(jué)得比較實(shí)用或者有哪些測(cè)試用例覺(jué)得比較難寫呢?
總結(jié)
以上是生活随笔為你收集整理的你可能会用到的 Mock 小技巧的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 快速排序的性能和名字一样优秀
- 下一篇: 使用 Azure Container R