无法实例化HttpRequest编写Azure Function单元测试的实现方案
HttpRequest依赖方法的Azure Function单元测试实现
问题场景
在为依赖Microsoft.AspNetCore.Http命名空间下HttpRequest类型参数的方法编写单元测试时,由于HttpRequest是抽象类无法直接实例化,且直接测试Azure Function入口时,方法仅返回OkObjectResult或BadObjectResult类型结果,无法校验内部参数解析逻辑的传值正确性,需要覆盖参数解析方法的全部分支判断逻辑。
待测试的参数解析方法如下:
public static RequestParameters GetInputParameters(HttpRequest req) { RequestParameters requestParameters = new RequestParameters(); if (req.GetQueryParameterDictionary().ContainsKey("Models")) { string models = string.IsNullOrEmpty(req.GetQueryParameterDictionary()["Models"]) ? Constants.Constants.ModelsDefaultValue : req.GetQueryParameterDictionary()["Models"]; List<string> modelList = new List<string>(); var getModelList = models?.Split(new char[] { ',' }); if (getModelList == null) { modelList.Add(Constants.Constants.ModelsDefaultValue); } else if (getModelList.Length == 1) { modelList.Add(getModelList[0]); } else { foreach (var item in getModelList) { Match match = Regex.Match(item, @"'([^']*)"); if (match.Success) { modelList.Add(match.Groups[1].Value); } } } requestParameters.modelList = modelList; } else { requestParameters.modelList.Add(Constants.Constants.ModelsDefaultValue); } if (req.GetQueryParameterDictionary().ContainsKey("timestamp")) { requestParameters.timestamp = string.IsNullOrEmpty(req.GetQueryParameterDictionary()["timestamp"]) ? Constants.Constants.TimestampDefaultValue : req.GetQueryParameterDictionary()["timestamp"]; } else { requestParameters.timestamp = Constants.Constants.TimestampDefaultValue; } if (req.GetQueryParameterDictionary().ContainsKey("timespan")) { requestParameters.timespan = string.IsNullOrEmpty(req.GetQueryParameterDictionary()["timespan"]) ? Constants.Constants.TimeSpanDefaultValue : req.GetQueryParameterDictionary()["timespan"]; } else { requestParameters.timespan = Constants.Constants.TimeSpanDefaultValue; } if (req.GetQueryParameterDictionary().ContainsKey("variable")) { requestParameters.variable = string.IsNullOrEmpty(req.GetQueryParameterDictionary()["variable"]) ? "severity" : req.GetQueryParameterDictionary()["variable"]; } else { requestParameters.variable = "severity"; } return requestParameters; }
Azure Function入口定义如下:
public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "variables/assetTrend")] HttpRequest req, ILogger log)
实现方案
方案1:直接对静态参数解析方法做单元测试(推荐,分支覆盖成本最低)
不需要通过Function入口间接测试GetInputParameters方法,该方法是公开静态方法,可直接作为测试目标。构造测试用HttpRequest实例不需要使用Mock框架,直接使用ASP.NET Core自带的DefaultHttpContext生成真实的请求对象即可,比Mock配置更简单、运行更稳定:
- 构造测试请求的核心代码:
// 初始化默认Http上下文,获取真实的HttpRequest实例 var httpContext = new DefaultHttpContext(); HttpRequest testReq = httpContext.Request; // 按需配置QueryString覆盖不同测试分支 // 场景1:所有参数都不存在,走默认值逻辑 testReq.QueryString = new QueryString(""); // 场景2:所有参数为空字符串,走默认值逻辑 testReq.QueryString = new QueryString("?Models=×tamp=×pan=&variable="); // 场景3:Models为单值,其余参数传自定义值 testReq.QueryString = new QueryString("?Models=testModel×tamp=1690000000×pan=1h&variable=temperature"); // 场景4:Models为多值带单引号格式 testReq.QueryString = new QueryString("?Models='modelA','modelB','modelC'");
针对每个测试场景构造对应Query的请求实例后,直接调用GetInputParameters(testReq),断言返回的RequestParameters对象各属性值是否符合预期,即可覆盖所有分支判断逻辑,完全不依赖Function入口的返回结果。
方案2:入口级联测试时直接解析ObjectResult的Value属性
如果需要连带测试Function Run入口的处理逻辑,不需要猜测内部变量传值,拿到返回的IActionResult后直接转型读取内部承载的返回值即可:
// 执行Function入口方法 IActionResult runResult = await functionInstance.Run(testReq, Mock.Of<ILogger>()); // 断言成功场景返回值 if (runResult is OkObjectResult okRes) { var returnData = okRes.Value; // 直接读取返回的业务对象,断言内部字段即可 } // 断言失败场景返回值 if (runResult is BadRequestObjectResult badRes) { var errorMsg = badRes.Value; // 错误场景返回内容也可直接读取校验 }
注意事项
- 不要尝试用Mock框架直接Mock HttpRequest的Query相关逻辑:
GetQueryParameterDictionary()是扩展方法,Mock框架无法直接Mock扩展方法的实现,强行配置需要模拟大量内部依赖,效率远低于使用DefaultHttpContext构造真实请求实例。 - 用例设计时按参数维度覆盖即可:每个查询参数分别覆盖「参数不存在」「参数为空值」「参数传入合法值」三种场景,Models参数额外覆盖「多值带单引号」「多值无匹配单引号格式」两个分支,即可实现100%分支覆盖。
内容的提问来源于stack exchange,提问作者Ayushi Gupta
相关产品推荐
相关产品推荐

