ich Unit-Test von MusiStore Beispiel ein einfaches Verfahren wie Login
dieses in meinem AccountController
basierend auf this Test versuchen.Wie man richtig IAuthenticationHandler imitieren, während Unit-Tests ASP.NET Core-Controller
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginArgumentsModel model)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return Ok();
}
return StatusCode(422); // Unprocessable Entity
}
Dafür muss ich verwenden beide UserManager
und SignInManager
, die mich schließlich für IAuthenticationHandler
einen Ersatz schreiben zu verwenden Kräfte in HttpAuthenticationFeature
verwenden. Am Ende dreht Test wie folgt aus:
public class AccountControllerTestsFixture : IDisposable
{
public IServiceProvider BuildServiceProvider(IAuthenticationHandler handler)
{
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider();
var services = new ServiceCollection();
services.AddOptions();
services.AddDbContext<ApplicationDbContext>(b => b.UseInMemoryDatabase().UseInternalServiceProvider(efServiceProvider));
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 3;
}).AddEntityFrameworkStores<ApplicationDbContext>();
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature { Handler = handler });
services.AddSingleton<IHttpContextAccessor>(new HttpContextAccessor()
{
HttpContext = context
});
return services.BuildServiceProvider();
}
public Mock<IAuthenticationHandler> MockSignInHandler()
{
var handler = new Mock<IAuthenticationHandler>();
handler.Setup(o => o.AuthenticateAsync(It.IsAny<AuthenticateContext>())).Returns<AuthenticateContext>(c =>
{
c.NotAuthenticated();
return Task.FromResult(0);
});
handler.Setup(o => o.SignInAsync(It.IsAny<SignInContext>())).Returns<SignInContext>(c =>
{
c.Accept();
return Task.FromResult(0);
});
return handler;
}
public void Dispose(){}
}
und diese:
public class AccountControllerTests : IClassFixture<AccountControllerTestsFixture>
{
private AccountControllerTestsFixture _fixture;
public AccountControllerTests(AccountControllerTestsFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Login_When_Present_Provider_Version()
{
// Arrange
var mockedHandler = _fixture.MockSignInHandler();
IServiceProvider serviceProvider = _fixture.BuildServiceProvider(mockedHandler.Object);
var userName = "Flattershy";
var userPassword = "Angel";
var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, userName) };
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var userManagerResult = await userManager.CreateAsync(new ApplicationUser() { Id = userName, UserName = userName, TwoFactorEnabled = false }, userPassword);
Assert.True(userManagerResult.Succeeded);
var signInManager = serviceProvider.GetRequiredService<SignInManager<ApplicationUser>>();
AccountController controller = new AccountController(userManager, signInManager);
// Act
var model = new LoginArgumentsModel { UserName = userName, Password = userPassword };
var result = await controller.Login(model) as Microsoft.AspNetCore.Mvc.StatusCodeResult;
// Assert
Assert.Equal((int)System.Net.HttpStatusCode.OK, result.StatusCode);
}
}
Sowohl mehrere spöttische IAuthenticationHandler
und mehrere Klassen zu schaffen Implementierung IAuthenticationHandler
für jeden Test auf unterschiedliche Weise ein wenig zu weit für mich sieht, aber ich möchte auch serviceProvider verwenden und nicht userManager
und signInManager
vortäuschen wollen. Während auf diese Weise geschriebene Tests zu funktionieren scheinen, möchte ich wissen, ob es eine superkomplizierte Möglichkeit gibt, CookieAuthenticationHandler
oder irgendetwas anderes zu verwenden, das sich so verhält wie die Anwendung mit app.UseIdentity()
.
Ich kann und habe das ein paar Mal gemacht, aber es fühlt sich nicht richtig an und ich denke, dass solch ein Aufruf keinen verspotteten 'HttpContext' beeinflusst, so dass ein komplexerer Controller sich nicht richtig verhalten könnte. Außerdem möchte ich, dass es dem ursprünglichen Verhalten so nahe wie möglich kommt, und ich bin nicht sehr gut darin, Objekte zu verspotten. – FluffyOwl