Scu*_*eve 12 c# unit-testing asp.net-core asp.net-core-middleware asp.net-core-authenticationhandler
您如何对继承自 的自定义中间件进行单元测试AuthenticationHandler<AuthenticationSchemeOptions>?
我从它继承的自定义类用于基本身份验证。
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IProvidePrincipal _principalProvider;
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IProvidePrincipal principalProvider)
: base(options, logger, encoder, clock)
{
_principalProvider = principalProvider;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Request.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorizationHeader))
{
if (Credentials.TryParse(authorizationHeader, out Credentials credentials))
{
var principal = await _principalProvider.GetClaimsPrincipalAsync(credentials.Username, credentials.Password, Scheme.Name);
if (principal != null)
{
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
else
{
return AuthenticateResult.Fail("Basic authentication failed. Invalid username and password.");
}
}
else
{
return AuthenticateResult.Fail("Basic authentication failed. Unable to parse username and password.");
}
}
return AuthenticateResult.Fail("Basic authentication failed. Authorization header is missing.");
}
}
Run Code Online (Sandbox Code Playgroud)
Scu*_*eve 30
单元测试自定义中间件相对容易,但是当您从 继承时AuthenticationHandler,基类在其中抛出了一个扳手。在到处寻找并且只找到集成测试之后,我终于能够弄清楚如何去做。
单元测试的基本设置不会每次测试都改变。
[TestClass]
public class BasicAuthenticationTests
{
private readonly Mock<IOptionsMonitor<AuthenticationSchemeOptions>> _options;
private readonly Mock<ILoggerFactory> _loggerFactory;
private readonly Mock<UrlEncoder> _encoder;
private readonly Mock<ISystemClock> _clock;
private readonly Mock<IProvidePrincipal> _principalProvider;
private readonly BasicAuthenticationHandler _handler;
public BasicAuthenticationTests()
{
_options = new Mock<IOptionsMonitor<AuthenticationSchemeOptions>>();
// This Setup is required for .NET Core 3.1 onwards.
_options
.Setup(x => x.Get(It.IsAny<string>()))
.Returns(new AuthenticationSchemeOptions());
var logger = new Mock<ILogger<BasicAuthenticationHandler>>();
_loggerFactory = new Mock<ILoggerFactory>();
_loggerFactory.Setup(x => x.CreateLogger(It.IsAny<String>())).Returns(logger.Object);
_encoder = new Mock<UrlEncoder>();
_clock = new Mock<ISystemClock>();
_principalProvider = new Mock<IProvidePrincipal>();
_handler = new BasicAuthenticationHandler(_options.Object, _loggerFactory.Object, _encoder.Object, _clock.Object, _principalProvider.Object);
}
Run Code Online (Sandbox Code Playgroud)
特别注意的_loggerFactory.Setup(x => x.CreateLogger(It.IsAny<String>())).Returns(logger.Object);
。如果你不这样做,你的处理器在代码中的空引用完成后,你不能调试你的单元测试将炸弹。这是因为基类CreateLogger在其构造函数中调用。
现在,您可以设置DefaultHttpContext用于测试逻辑的上下文。
[TestMethod]
public async Task HandleAuthenticateAsync_NoAuthorizationHeader_ReturnsAuthenticateResultFail()
{
var context = new DefaultHttpContext();
await _handler.InitializeAsync(new AuthenticationScheme(BasicAuthenticationHandler.SchemeName, null, typeof(BasicAuthenticationHandler)), context);
var result = await _handler.AuthenticateAsync();
Assert.IsFalse(result.Succeeded);
Assert.AreEqual("Basic authentication failed. Authorization header is missing.", result.Failure.Message);
}
Run Code Online (Sandbox Code Playgroud)
请注意,您不能HandleAuthenticateAsync直接调用,因为它是受保护的。处理程序必须先初始化然后调用AuthenticateAsync。
我在下面包含了要测试的其余逻辑,以举例说明如何操作上下文并对不同测试场景的结果进行断言。
[TestMethod]
public async Task HandleAuthenticateAsync_CredentialsTryParseFails_ReturnsAuthenticateResultFail()
{
var context = new DefaultHttpContext();
var authorizationHeader = new StringValues(String.Empty);
context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader);
await _handler.InitializeAsync(new AuthenticationScheme(BasicAuthenticationHandler.SchemeName, null, typeof(BasicAuthenticationHandler)), context);
var result = await _handler.AuthenticateAsync();
Assert.IsFalse(result.Succeeded);
Assert.AreEqual("Basic authentication failed. Unable to parse username and password.", result.Failure.Message);
}
[TestMethod]
public async Task HandleAuthenticateAsync_PrincipalIsNull_ReturnsAuthenticateResultFail()
{
_principalProvider.Setup(m => m.GetClaimsPrincipalAsync(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<String>())).ReturnsAsync((ClaimsPrincipal)null);
var context = new DefaultHttpContext();
var authorizationHeader = new StringValues("Basic VGVzdFVzZXJOYW1lOlRlc3RQYXNzd29yZA==");
context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader);
await _handler.InitializeAsync(new AuthenticationScheme(BasicAuthenticationHandler.SchemeName, null, typeof(BasicAuthenticationHandler)), context);
var result = await _handler.AuthenticateAsync();
Assert.IsFalse(result.Succeeded);
Assert.AreEqual("Basic authentication failed. Invalid username and password.", result.Failure.Message);
}
[TestMethod]
public async Task HandleAuthenticateAsync_PrincipalIsNull_ReturnsAuthenticateResultSuccessWithPrincipalInTicket()
{
var username = "TestUserName";
var claims = new[] { new Claim(ClaimTypes.Name, username) };
var identity = new ClaimsIdentity(claims, BasicAuthenticationHandler.SchemeName);
var claimsPrincipal = new ClaimsPrincipal(identity);
_principalProvider.Setup(m => m.GetClaimsPrincipalAsync(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<String>())).ReturnsAsync(claimsPrincipal);
var context = new DefaultHttpContext();
var authorizationHeader = new StringValues("Basic VGVzdFVzZXJOYW1lOlRlc3RQYXNzd29yZA==");
context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader);
await _handler.InitializeAsync(new AuthenticationScheme(BasicAuthenticationHandler.SchemeName, null, typeof(BasicAuthenticationHandler)), context);
var result = await _handler.AuthenticateAsync();
Assert.IsTrue(result.Succeeded);
Assert.AreEqual(BasicAuthenticationHandler.SchemeName, result.Ticket.AuthenticationScheme);
Assert.AreEqual(username, result.Ticket.Principal.Identity.Name);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4362 次 |
| 最近记录: |