问题
SignInAsync() Source Code
I ran into some problems with unit testing.
DefaultHttpContext.RequestServicesis null- I tried to create the
AuthenticationServiceobject, but I do not know what parameters to pass
What should I do? How to unit test HttpContext.SignInAsync()?
Method under test
public async Task<IActionResult> Login(LoginViewModel vm, [FromQuery]string returnUrl)
{
if (ModelState.IsValid)
{
var user = await context.Users.FirstOrDefaultAsync(u => u.UserName == vm.UserName && u.Password == vm.Password);
if (user != null)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName)
};
var identity = new ClaimsIdentity(claims, "HappyDog");
// here
await HttpContext.SignInAsync(new ClaimsPrincipal(identity));
return Redirect(returnUrl ?? Url.Action("Index", "Goods"));
}
}
return View(vm);
}
What I have tried so far.
[TestMethod]
public async Task LoginTest()
{
using (var context = new HappyDogContext(_happyDogOptions))
{
await context.Users.AddAsync(new User { Id = 1, UserName = "test", Password = "password", FacePicture = "FacePicture" });
await context.SaveChangesAsync();
var controller = new UserController(svc, null)
{
ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
// How mock RequestServices?
// RequestServices = new AuthenticationService()?
}
}
};
var vm = new LoginViewModel { UserName = "test", Password = "password" };
var result = await controller.Login(vm, null) as RedirectResult;
Assert.AreEqual("/Goods", result.Url);
}
}
回答1:
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 could just as easily mocked the HttpContext as well like the other dependencies.
You can read up on how to use Moq here at their Quick start
回答2:
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
回答3:
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);
来源:https://stackoverflow.com/questions/47198341/how-to-unit-test-httpcontext-signinasync