How to unit test HttpContext.SignInAsync()?
HttpContext.SignInAsync
is an extension method that uses RequestServices
, which is IServiceProvider
. That is what you must mock.
context.RequestServices
.GetRequiredService<IAuthenticationService>()
.SignInAsync(context, scheme, principal, properties);
You can either create a fake/mock manually by creating classes that derive from the used interfaces or use a mocking framework like Moq
//...code removed for brevity
var authServiceMock = new Mock<IAuthenticationService>();
authServiceMock
.Setup(_ => _.SignInAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>()))
.Returns(Task.FromResult((object)null));
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock
.Setup(_ => _.GetService(typeof(IAuthenticationService)))
.Returns(authServiceMock.Object);
var controller = new UserController(svc, null) {
ControllerContext = new ControllerContext {
HttpContext = new DefaultHttpContext {
// How mock RequestServices?
RequestServices = serviceProviderMock.Object
}
}
};
//...code removed for brevity
You can read up on how to use Moq here at their Quick start
You could just as easily mocked the HttpContext
as well like the other dependencies but if a default implementation exists that causes no undesired behavior, then using that can make things a lot simpler to arrange
For example, an actual IServiceProvider
could have been used by building one via ServiceCollection
//...code removed for brevity
var authServiceMock = new Mock<IAuthenticationService>();
authServiceMock
.Setup(_ => _.SignInAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>()))
.Returns(Task.FromResult((object)null));
var services = new ServiceCollection();
services.AddSingleton<IAuthenticationService>(authServiceMock.Object);
var controller = new UserController(svc, null) {
ControllerContext = new ControllerContext {
HttpContext = new DefaultHttpContext {
// How mock RequestServices?
RequestServices = services.BuildServiceProvider();
}
}
};
//...code removed for brevity
That way if there are other dependencies, they can be mocked and registered with the service collection so that they can be resolved as needed.
In case you guys are looking for NSubstitue example (Asp.net core).
IAuthenticationService authenticationService = Substitute.For<IAuthenticationService>();
authenticationService
.SignInAsync(Arg.Any<HttpContext>(), Arg.Any<string>(), Arg.Any<ClaimsPrincipal>(),
Arg.Any<AuthenticationProperties>()).Returns(Task.FromResult((object) null));
var serviceProvider = Substitute.For<IServiceProvider>();
var authSchemaProvider = Substitute.For<IAuthenticationSchemeProvider>();
var systemClock = Substitute.For<ISystemClock>();
authSchemaProvider.GetDefaultAuthenticateSchemeAsync().Returns(Task.FromResult
(new AuthenticationScheme("idp", "idp",
typeof(IAuthenticationHandler))));
serviceProvider.GetService(typeof(IAuthenticationService)).Returns(authenticationService);
serviceProvider.GetService(typeof(ISystemClock)).Returns(systemClock);
serviceProvider.GetService(typeof(IAuthenticationSchemeProvider)).Returns(authSchemaProvider);
context.RequestServices.Returns(serviceProvider);
// Your act goes here
// Your assert goes here
This didn't work for me in .NET Core 2.2 - it still expects another interface: ISystemClock. So I simply decided to take another approach, namely to wrap the entire thing, like this:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
namespace Utilities.HttpContext
{
public interface IHttpContextWrapper
{
Task SignInAsync(Controller controller, string subject, string name, AuthenticationProperties props);
}
}
...and then I have one implementation for normal use and on for test.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
namespace Utilities.HttpContext
{
public class DefaultHttpContextWrapper : IHttpContextWrapper
{
public async Task SignInAsync(Controller controller, string subject, string name, AuthenticationProperties props)
{
await controller.HttpContext.SignInAsync(subject, name, props);
}
}
}
...and the fake implementation:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
namespace Utilities.HttpContext
{
public class FakeHttpContextWrapper : IHttpContextWrapper
{
public Task SignInAsync(Controller controller, string subject, string name, AuthenticationProperties props)
{
return Task.CompletedTask;
}
}
}
Then I just inject the desired implementation as the interface in the controller's constructor using .NET Core's native DI container (in Startup.cs).
services.AddScoped<IHttpContextWrapper, DefaultHttpContextWrapper>();
Finally, the call looks like this (passing in my controller with this):
await _httpContextWrapper.SignInAsync(this, user.SubjectId, user.Username, props);