C# 如何对HttpContext.SignInAsync()进行单元测试?
我在单元测试中遇到了一些问题C# 如何对HttpContext.SignInAsync()进行单元测试?,c#,unit-testing,asp.net-core,asp.net-core-mvc,asp.net-core-2.0,C#,Unit Testing,Asp.net Core,Asp.net Core Mvc,Asp.net Core 2.0,我在单元测试中遇到了一些问题 DefaultHttpContext.RequestServices为空 我试图创建AuthenticationService对象,但不知道要传递哪些参数 我该怎么办?如何单元测试HttpContext.SignInAsync() 测试中的方法 public async Task<IActionResult> Login(LoginViewModel vm, [FromQuery]string returnUrl) { if (ModelStat
DefaultHttpContext.RequestServices
为空AuthenticationService
对象,但不知道要传递哪些参数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);
}
HttpContext.SignInAsync
是一种使用RequestServices
的扩展方法,即IServiceProvider
。这就是你必须嘲笑的
context.RequestServices
.GetRequiredService<IAuthenticationService>()
.SignInAsync(context, scheme, principal, properties);
你可以在他们的网站上了解如何使用最小起订量
您可以像模拟其他依赖项一样轻松地模拟HttpContext
,但是如果存在一个不会导致不希望出现的行为的默认实现,那么使用它可以使安排工作变得更加简单
例如,实际的IServiceProvider
可以通过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
//…为简洁起见删除了代码
var authServiceMock=new Mock();
authServiceMock
.Setup(=>u.SignInAsync(It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny())
.Returns(Task.FromResult((object)null));
var services=newservicecolection();
AddSingleton(authServiceMock.Object);
var控制器=新用户控制器(svc,null){
ControllerContext=新的ControllerContext{
HttpContext=新的DefaultHttpContext{
//如何模拟请求服务?
RequestServices=services.BuildServiceProvider();
}
}
};
//…为简洁起见,删除了代码
这样,如果存在其他依赖项,则可以模拟这些依赖项并在服务集合中注册,以便根据需要解决它们。如果你们正在寻找NSubstitue示例(Asp.net core)
iaauthenticationservice authenticationService=Substitute.For();
认证服务
.SignInAsync(Arg.Any(),Arg.Any(),Arg.Any(),Arg.Any(),
返回(Task.FromResult((object)null));
var serviceProvider=Substitute.For();
var authSchemaProvider=Substitute.For();
var systemClock=替换为();
authSchemaProvider.GetDefaultAuthenticateSchemeAsync()。返回(Task.FromResult
(新认证方案(“idp”、“idp”,
类型(IAAuthenticationHandler));
GetService(typeof(iaauthenticationservice))。返回(authenticationService);
GetService(typeof(ISystemClock)).Returns(systemClock);
GetService(typeof(iaAuthenticationSchemeProvider)).Returns(authSchemaProvider);
context.RequestServices.Returns(serviceProvider);
//你的表演到此为止
//你的断言在这里
这在.NET Core 2.2中对我不起作用-它仍然需要另一个接口:ISystemClock。因此,我决定采取另一种方法,即将整个过程包装起来,如下所示:
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);
}
}
…然后我有一个实现供正常使用并继续测试
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);
}
}
}
…以及伪实现:
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;
}
}
}
然后,我使用.NETCore的本机DI容器(在Startup.cs中)将所需的实现作为接口注入控制器的构造函数中
回答得很好。我在使用以下代码时出错:“System.InvalidOperationException:没有注册类型为“Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory”的服务。”我还通过模拟ITempDataDictionary并将其分配给控制器来解决这个问题。TempData@JasonLearmouth此回答针对OP的特定目标类型
iaAuthenticationService
,如果没有使用模拟设置任何其他类型,则会失败。当get服务调用返回null时,您得到的错误是默认错误。@Nkosi,我同意,这就是为什么我对您的答案投了赞成票而没有编辑它的原因。我发布了关于错误和解决方案的评论,因为仅仅根据错误消息,不太容易确定要模拟什么以及在哪里分配。我认为不应该模拟IdentityServer扩展方法中的依赖项。如果代码更改,那么测试将中断。在HttpContext上创建一个包装器,并测试是否执行了包装器上的方法,这样会更干净、更安全。要测试您实际登录的事实,可以通过集成测试来完成。对于我来说,可接受的解决方案在Core2.2中运行良好,我个人认为这是一种更好的方法。我认为您不应该在IdentityServer扩展方法中模拟依赖项。如果代码更改,那么测试将中断。在HttpContext上创建包装更干净、更安全
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);
}
}
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);
}
}
}
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;
}
}
}
services.AddScoped<IHttpContextWrapper, DefaultHttpContextWrapper>();
await _httpContextWrapper.SignInAsync(this, user.SubjectId, user.Username, props);