引入Microsoft.Identity.Web后Azure Function单元测试触发TypeLoadException
我在对一个读取查询字符串参数的Azure Function做单元测试,引入Microsoft.Identity.Web包后,原本正常的测试抛出TypeLoadException,无法加载内部的QueryCollection或DefaultHttpRequest类型,以下是详细信息:
目标Function代码
[FunctionName("GetEntityId")] public async Task<IActionResult> GetEntityId( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "entityid")] HttpRequest req) { if (!int.TryParse(req.Query["quantity"], out int quantity)) { quantity = 1; } quantity = Math.Min(quantity, 100); var entityIds = await this.entityIdGenerator.NewEntityIdAsync(userId, quantity); if (entityIds is null) { return new System.Web.Http.InternalServerErrorResult(); } else { return new OkObjectResult(entityIds); } }
原单元测试实现(引入包前正常)
之前通过Moq模拟HttpRequest和HttpContext,代码如下:
var reqMock = new Mock<HttpRequest>(); var httpContextMock = new Mock<HttpContext>(); var responseMock = new Mock<HttpResponse>(); responseMock.Setup(res => res.Headers).Returns(new HeaderDictionary()); httpContextMock.Setup(http => http.Response).Returns(responseMock.Object); httpContextMock.Setup(http => http.Request).Returns(reqMock.Object);
通过内部类QueryCollection模拟查询字符串:
reqMock.Setup(req => req.Query).Returns(new QueryCollection(queryString));
报错信息
引入Microsoft.Identity.Web后,单元测试触发以下错误:
System.TypeLoadException: 'Could not load type 'Microsoft.AspNetCore.Http.Internal.QueryCollection' from assembly 'Microsoft.AspNetCore.Http, Version=3.1.31.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.'
尝试改用DefaultHttpRequest和DefaultHttpContext时,出现类似错误:
Could not load type 'Microsoft.AspNetCore.Http.Internal.DefaultHttpRequest' from assembly 'Microsoft.AspNetCore.Http, Version=3.1.31.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.'
已尝试更新所有相关包到最新版本,问题依旧。
项目CSPROJ包引用
<ItemGroup> <PackageReference Include="AutoMapper" Version="11.0.1" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Avatar.Infrastructure.EfCore" Version="1.0.0-CI-20230130-181631" /> <PackageReference Include="Azure.Storage.Blobs" Version="12.6.0" /> <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" /> <PackageReference Include="Microsoft.Azure.WebJobs.Logging.ApplicationInsights" Version="3.0.30" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.28" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.28" /> <PackageReference Include="Microsoft.Identity.Client" Version="4.49.1" /> <PackageReference Include="Microsoft.Identity.Web" Version="1.25.10" /> <PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.9" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.26.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" /> <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.13" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <AdditionalFiles Include="stylecop.json" /> </ItemGroup>
完整单元测试代码
[TestCategory("AvatarFunctionAppTest Story 893")] [TestMethod] public async Task GetEntityIdNotAuthorised() { HttpRequest mockedRequest = this.HttpRequestSetup(); var result = await new AvatarFunctionApp( this.EasyAuthProxy, this.EntityIdGenerator, this.TableStorage, this.mockSignalService.Object, this.log.Object) .GetEntityId(mockedRequest); Assert.IsTrue(result is UnauthorizedResult); } public HttpRequest HttpRequestSetup(IHeaderDictionary headers = null) { return this.HttpRequestSetup(new Dictionary<string, StringValues>(), headers); } public HttpRequest HttpRequestSetup(Dictionary<string, StringValues> queryString, IHeaderDictionary headers = null, Uri uri = null) { var reqMock = new Mock<HttpRequest>(); var httpContextMock = new Mock<HttpContext>(); var responseMock = new Mock<HttpResponse>(); responseMock.Setup(res => res.Headers).Returns(new HeaderDictionary()); httpContextMock.Setup(http => http.Response).Returns(responseMock.Object); httpContextMock.Setup(http => http.Request).Returns(reqMock.Object); reqMock.Setup(req => req.Query).Returns(new QueryCollection(queryString)); reqMock.Setup(req => req.Body).Returns(default(Stream)); reqMock.Setup(req => req.Headers).Returns(headers ?? new HeaderDictionary()); reqMock.Setup(req => req.HttpContext).Returns(httpContextMock.Object); if (uri is Uri) { reqMock.Setup(req => req.Scheme).Returns(uri.Scheme); reqMock.Setup(req => req.Host).Returns(new HostString(uri.Host, uri.Port)); reqMock.Setup(req => req.Path).Returns(uri.AbsolutePath); } this.mockHttpContextAccessor.Setup(hca => hca.HttpContext).Returns(httpContextMock.Object); return reqMock.Object; }
解决方案
1. 避免使用内部类型,改用公开API构造查询参数
Microsoft.AspNetCore.Http.Internal.QueryCollection是内部类型,版本更新或依赖冲突时容易失效。改用公开API构造IQueryCollection:
// 替代new QueryCollection(queryString)的写法 var query = QueryString.Create(queryString); var queryCollection = query.ParseNullable(); reqMock.Setup(req => req.Query).Returns(queryCollection);
或者直接模拟IQueryCollection的行为,避免实例化具体内部类:
var queryMock = new Mock<IQueryCollection>(); queryMock.Setup(q => q["quantity"]).Returns("5"); reqMock.Setup(req => req.Query).Returns(queryMock.Object);
2. 统一依赖版本,解决冲突
检查项目依赖中Microsoft.AspNetCore.Http的版本,确保所有引用该包的依赖使用同一版本。可以在CSPROJ中添加版本绑定:
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Http" Version="3.1.31" /> </ItemGroup>
同时更新Microsoft.NET.Sdk.Functions到兼容版本(如3.0.31或更高),确保与Microsoft.Identity.Web的依赖版本匹配。
3. 使用DefaultHttpContext的正确方式
如果需要使用真实的HttpRequest实例,不要直接引用内部的DefaultHttpRequest,而是通过DefaultHttpContext获取:
var httpContext = new DefaultHttpContext(); httpContext.Request.Query = QueryString.Create(queryString).ParseNullable(); // 直接使用httpContext.Request作为测试的HttpRequest
确保项目中引用的Microsoft.AspNetCore.Http版本与DefaultHttpContext兼容,避免版本不匹配导致的类型加载错误。
内容的提问来源于stack exchange,提问作者Pat Long - Munkii Yebee

