在C#单元测试中使用HttpContext.GetTokenAsync

在C#单元测试中使用HttpContext.GetTokenAsync,c#,unit-testing,moq,xunit,httpcontext,C#,Unit Testing,Moq,Xunit,Httpcontext,我正在尝试为我的控制器类编写单元测试,该类使用以下命令检索令牌: string token = await HttpContext.GetTokenAsync("access_token"); 因此,我用以下代码模拟了HttpContext: public static HttpContext MakeFakeContext() { var serviceProvider = new Mock<IServiceProvider>(); var authservice

我正在尝试为我的控制器类编写单元测试,该类使用以下命令检索令牌:

string token = await HttpContext.GetTokenAsync("access_token");
因此,我用以下代码模拟了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
    };
}
现在,当我运行单元测试时,我得到以下错误:

System.NotSupportedException:不受支持的表达式:=>U7.GetTokenAsync(It.IsAny(),It.IsAny()) 在设置/验证表达式中不能使用扩展方法(此处:AuthenticationTokenExtensions.GetTokenAsync)

在我的研究中,我偶然发现了一些解决方案,在这些解决方案中,您可以模拟引擎盖下涉及的特定部件,而这些部件不是扩展的一部分。这些是其中的一些:。第二个显示了一个类似的问题,在我尝试之后,它似乎起了作用。但由于某些原因,它不适用于GetTokenAsync方法


你们有什么线索吗

以下是该扩展方法的源代码:

public static Task<string> GetTokenAsync(this HttpContext context, string scheme, 
string tokenName) =>
    context.RequestServices.GetRequiredService<IAuthenticationService> 
    ().GetTokenAsync(context, scheme, tokenName);
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);
}
公共静态任务GetTokenAsync(此HttpContext上下文,字符串方案,
字符串标记名)=>
context.RequestServices.GetRequiredService
().GetTokenAsync(上下文、方案、令牌名);
然后调用此扩展方法:

public static Task<string> GetTokenAsync(this HttpContext context, string scheme, 
string tokenName) =>
    context.RequestServices.GetRequiredService<IAuthenticationService> 
    ().GetTokenAsync(context, scheme, tokenName);
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);
}
公共静态异步任务GetTokenAsync(此IAAuthenticationService身份验证、HttpContext上下文、字符串方案、字符串令牌名称)
{
if(auth==null)
{
抛出新ArgumentNullException(nameof(auth));
}
if(tokenName==null)
{
抛出新ArgumentNullException(nameof(tokenName));
}
var result=wait auth.authenticateSync(上下文、方案);
返回结果?.Properties?.GetTokenValue(tokenName);
}
最终结果是调用
authenticatesync
,如第
var result=wait auth.authenticatesync(context,scheme)行所示

既然您不能修改扩展方法,也许您可以编写自己的模拟方法

我不确定对具有扩展方法的对象进行模拟时的最佳实践是什么,因此可能有人可以对此答案进行扩展

值得注意的是
authenticateSync
不是扩展方法,您可以找到代码

正如@Nkosi所提到的:

Mock
IServiceProvider
IAAuthenticationService


IMHO,看到真正的代码总是很有用的,这样你就可以识别和理解它在做什么,并完全模拟出所有需要的部分,所以我将以上内容保留下来。

根据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);
    }
private void MockHttpContextGetToken(
模拟httpContextAccessorMock,
字符串令牌名称、字符串令牌值、字符串方案=null)
{
var authenticationServiceMock=new Mock();
httpContextAccessorMock
.Setup(x=>x.HttpContext.RequestServices.GetService(typeof(IAAuthenticationService)))
.Returns(authenticationServiceMock.Object);
var authResult=AuthenticateResult.Success(
新的身份验证票证(新的ClaimsPrincipal(),scheme));
authResult.Properties.StoreTokens(新[]
{
新身份验证令牌{Name=tokenName,Value=tokenValue}
});
authenticationServiceMock
.Setup(x=>x.authenticateSync(httpContextAccessorMock.Object.HttpContext,scheme))
.ReturnsAsync(authResult);
}
这对我很有用

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
};
controller.ControllerContext=新建ControllerContext();
var serviceProvider=new Mock();
var authenticationServiceMock=new Mock();
var authResult=AuthenticateResult.Success(
新身份验证票证(new-ClaimsPrincipal(),null));
authResult.Properties.StoreTokens(新[]
{
新身份验证令牌{Name=“access\u token”,Value=“accessTokenValue”}
});
authenticationServiceMock
.Setup(x=>x.AuthenticateAsync(It.IsAny(),null))
.ReturnsAsync(authResult);
serviceProvider.Setup(=>quot.GetService(typeof(iaauthenticationservice))).Returns(authenticationServiceMock.Object);
controller.ControllerContext.HttpContext=新的默认HttpContext
{
用户=用户,
RequestServices=serviceProvider.Object
};

您很可能仍在模拟其中一个扩展检查,以确保您正在模拟实例成员。还可以尝试使用链接示例中的DefaultHttpContext。减少了要进行的模拟量。@Nkosi,我有点困惑,因为SignInAsync不是扩展方法。对我来说,这似乎是它与SignInAsync一起工作的原因。如果你遵循源代码面包屑,它会引导你找到。模拟扩展正在使用的实例成员,您应该会得到所需的行为,如所提供的答案中所述。您需要模拟
authenticateSync
,这是扩展最终调用的。此setup.setup(x=>x.HttpContext.RequestServices.GetService(typeof(iaauthenticationservice)))“工作不在我这边吗