Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using HttpContext.GetTokenAsync in C# Unit Tests

I am trying to write unit tests for my controller class which retrieves a token with the following command:

string token = await HttpContext.GetTokenAsync("access_token");

Therefore I mocked the HttpContext with the following code:

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
    };
}

I am setting the mocked context with:

var mockcontext = MakeFakeContext();

unitUnderTest.ControllerContext = new ControllerContext
{
    HttpContext = mockcontext
};

Now when I run the unit test, I am getting the following error:

System.NotSupportedException : Unsupported expression: _ => _.GetTokenAsync(It.IsAny(), It.IsAny()) Extension methods (here: AuthenticationTokenExtensions.GetTokenAsync) may not be used in setup / verification expressions.

During my research I stumbled across solutions where you can mock specific parts that are involved under the hood which are not part of extensions. These are some of them: Moq IServiceProvider / IServiceScope, How to unit test HttpContext.SignInAsync()?. The second one shows a similar problem, which seems to work after I tried. But for some reason it does not work for the GetTokenAsync method.

Do you guys have any hint out there?

like image 588
Noah Ispas Avatar asked Dec 03 '22 17:12

Noah Ispas


2 Answers

Building on from Zer0's answer here is an example using 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);
    }
like image 134
Stacey Avatar answered Dec 20 '22 20:12

Stacey


Here's the source code for that extension method:

public static Task<string> GetTokenAsync(this HttpContext context, string scheme, 
string tokenName) =>
    context.RequestServices.GetRequiredService<IAuthenticationService> 
    ().GetTokenAsync(context, scheme, tokenName);

Which in turn calls this extension method:

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);
}

The end result is calling AuthenticateAsync as seen in the line var result = await auth.AuthenticateAsync(context, scheme);.

Since you can't modify extension methods, maybe you can write your own mocked ones?

I'm not exactly sure what the best practice is when doing a mock on an object that has extension methods so maybe someone can expand on this answer.

It is worth noting AuthenticateAsync is not an extension method and you can find the code here.

As mentioned by @Nkosi:

Mock IServiceProvider and IAuthenticationService.

IMHO, seeing the real code is always useful so you can identify and understand what it's doing under the covers and fully mock out all the pieces needed, so I'll leave the above up.

like image 45
Zer0 Avatar answered Dec 20 '22 20:12

Zer0