Noa*_*pas 4 c# unit-testing moq xunit httpcontext
我正在尝试为我的控制器类编写单元测试,它使用以下命令检索令牌:
string token = await HttpContext.GetTokenAsync("access_token");
Run Code Online (Sandbox Code Playgroud)
因此,我用以下代码模拟了 HttpContext:
public static HttpContext MakeFakeContext()
{
var serviceProvider = new Mock<IServiceProvider>();
var authservice = new Mock<IAuthenticationService>();
authservice.Setup(_ => _.GetTokenAsync(It.IsAny<HttpContext>(), It.IsAny<string>())).Returns(Task.FromResult("token"));
serviceProvider.Setup(_ => _.GetService(typeof(IAuthenticationService))).Returns(authservice);
return new DefaultHttpContext
{
RequestServices = serviceProvider.Object
};
}
Run Code Online (Sandbox Code Playgroud)
我正在设置模拟上下文:
var mockcontext = MakeFakeContext();
unitUnderTest.ControllerContext = new ControllerContext
{
HttpContext = mockcontext
};
Run Code Online (Sandbox Code Playgroud)
现在,当我运行单元测试时,出现以下错误:
System.NotSupportedException:不支持的表达式:_ => _.GetTokenAsync(It.IsAny(), It.IsAny()) 扩展方法(此处:AuthenticationTokenExtensions.GetTokenAsync)不能用于设置/验证表达式。
在我的研究过程中,我偶然发现了一些解决方案,您可以在其中模拟引擎盖下不属于扩展的特定部分。这些是其中一些:Moq IServiceProvider / IServiceScope,如何对 HttpContext.SignInAsync() 进行单元测试?. 第二个显示了类似的问题,在我尝试后似乎有效。但由于某种原因,它不适用于 GetTokenAsync 方法。
大家有什么提示吗?
以 Zer0 的回答为基础,这里是一个使用 Moq 的示例:
private void MockHttpContextGetToken(
Mock<IHttpContextAccessor> httpContextAccessorMock,
string tokenName, string tokenValue, string scheme = null)
{
var authenticationServiceMock = new Mock<IAuthenticationService>();
httpContextAccessorMock
.Setup(x => x.HttpContext.RequestServices.GetService(typeof(IAuthenticationService)))
.Returns(authenticationServiceMock.Object);
var authResult = AuthenticateResult.Success(
new AuthenticationTicket(new ClaimsPrincipal(), scheme));
authResult.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = tokenName, Value = tokenValue }
});
authenticationServiceMock
.Setup(x => x.AuthenticateAsync(httpContextAccessorMock.Object.HttpContext, scheme))
.ReturnsAsync(authResult);
}
Run Code Online (Sandbox Code Playgroud)
小智 7
这对我有用
controller.ControllerContext = new ControllerContext();
var serviceProvider = new Mock<IServiceProvider>();
var authenticationServiceMock = new Mock<IAuthenticationService>();
var authResult = AuthenticateResult.Success(
new AuthenticationTicket(new ClaimsPrincipal(), null));
authResult.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = "accessTokenValue" }
});
authenticationServiceMock
.Setup(x => x.AuthenticateAsync(It.IsAny<HttpContext>(), null))
.ReturnsAsync(authResult);
serviceProvider.Setup(_ => _.GetService(typeof(IAuthenticationService))).Returns(authenticationServiceMock.Object);
controller.ControllerContext.HttpContext = new DefaultHttpContext
{
User = user,
RequestServices = serviceProvider.Object
};
Run Code Online (Sandbox Code Playgroud)
这是该扩展方法的源代码:
public static Task<string> GetTokenAsync(this HttpContext context, string scheme,
string tokenName) =>
context.RequestServices.GetRequiredService<IAuthenticationService>
().GetTokenAsync(context, scheme, tokenName);
Run Code Online (Sandbox Code Playgroud)
依次调用此扩展方法:
public static async Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string scheme, string tokenName)
{
if (auth == null)
{
throw new ArgumentNullException(nameof(auth));
}
if (tokenName == null)
{
throw new ArgumentNullException(nameof(tokenName));
}
var result = await auth.AuthenticateAsync(context, scheme);
return result?.Properties?.GetTokenValue(tokenName);
}
Run Code Online (Sandbox Code Playgroud)
最终结果是调用,AuthenticateAsync如行中所示var result = await auth.AuthenticateAsync(context, scheme);。
既然你不能修改扩展方法,也许你可以编写自己的模拟方法?
我不确定在对具有扩展方法的对象进行模拟时的最佳实践是什么,所以也许有人可以扩展这个答案。
值得注意的AuthenticateAsync是不是一个扩展方法,你可以在这里找到代码。
正如@Nkosi 所提到的:
模拟IServiceProvider和IAuthenticationService。
恕我直言,看到真正的代码总是有用的,这样你就可以识别和理解它在幕后做了什么,并完全模拟出所有需要的部分,所以我会留下上面的内容。