I am trying to UnitTest an Azure Function that reads a parameter directly from the querystring
[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);
}
}
There are unit tests that have mocked the HttpRequest and 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);
The mock for the querystring uses QueryCollection
reqMock.Setup(req => req.Query).Returns(new QueryCollection(queryString));
Up until recently this worked fine but since I introduced Microsoft.Identity.Web to the Azure Function I get the following error. If I remove the package reference the Unit Test passes
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'.'
I realise that using a class inside an Internal MS library looks wrong but this is one of the main ways to mock the HttpRequest and was working
If I try and used DefaultHttpRequest and DefaultHttpContext I get a different error but for a similar reason
Could not load type 'Microsoft.AspNetCore.Http.Internal.DefaultHttpRequest' from assembly 'Microsoft.AspNetCore.Http, Version=3.1.31.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.'
Something in the Microsoft.Identity library is causing a different dependent library to be called from what I can tell. I have tried updating Microsoft.Identity to latest but same error
The packages in the CSPROJ file are
<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>
The full UnitTest code is
[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;
}
I stopped trying to create a QueryCollection obect for use in my Mock setup and instead created my own FakeQueryCollection that implements IQueryCollection
internal class FakeQueryCollection : IQueryCollection
{
private Dictionary<string, StringValues> query;
public FakeQueryCollection(Dictionary<string, StringValues> query)
{
this.query = query;
}
public FakeQueryCollection()
{
this.query = new Dictionary<string, StringValues>();
}
public StringValues this[string key]
{
get
{
return this.query[key];
}
set
{
this.query[key] = value;
}
}
public int Count => this.query.Count;
public ICollection<string> Keys => this.query.Keys;
public bool ContainsKey(string key)
{
return this.query.ContainsKey(key);
}
public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator()
{
foreach (KeyValuePair<string, StringValues> pair in this.query)
{
yield return new KeyValuePair<string, StringValues>(pair.Key, pair.Value);
}
}
public bool TryGetValue(string key, out StringValues value)
{
return this.query.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.query.GetEnumerator();
}
}