Search code examples
c#unit-testingmoqhttpcontext

When mocking HttpContext, SignOutAsync fails


I'm using SignOutAsync from Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.

In my unit tests, I get an ArgumentNullException somewhere within that extension method, which says

Value cannot be null. (Parameter 'provider')

  Message: 
System.ArgumentNullException : Value cannot be null. (Parameter 'provider')

  Stack Trace: 
ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
AuthenticationHttpContextExtensions.SignOutAsync(HttpContext context, String scheme, AuthenticationProperties properties)
AuthenticationHttpContextExtensions.SignOutAsync(HttpContext context, String scheme)
AccountController.SignOut() line 123
AccountControllerTests.MyUnitTest() line 456
--- End of stack trace from previous location ---

I'm not directly providing 'provider', so I assume the extension method is building it from the HttpContext.

The HttpContext in my tests is created using Moq:

var httpContext = new Mock<HttpContext>();

Is there a way I can coax my mock into providing a value for 'provider'? Do I need to switch to a DefaultHttpContext, and provide a value for ServiceScopeFactory? (Tricky, because DefaultHttpContext isn't mock-able.)


Solution

  • If you look at the source, SignOutAsync will eventually try to call a private method GetAuthenticationService, and that tries to call context.RequestServices.GetService<IAuthenticationService>().

    The generic GetService is an extension method, so your null provider really is RequestServices. RequestServices is abstract on HttpContext, so it will be implemented by Moq, and by default on a loose mock, that will simply return null. Oops, there's your crash.

    So, could set it up:

    IServiceProvider serviceProvider = /*...*/;
    
    httpContext.SetupGet(x => x.RequestServices).Returns(serviceProvider);
    

    But by then you could just use a DefaultHttpContext and set the provider:

    var httpContext = new DefaultHttpContext
    {
        RequestServices = serviceProvider
    };
    

    And now all you need is a service provider. For that, see this question (you can mock one, or create one from a ServiceCollection, up to you).