Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/314.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# ASP.Net Core 2.0 SignInAsync返回的异常值不能为null,提供程序_C#_Asp.net Core 2.0_Nunit 3.0 - Fatal编程技术网

C# ASP.Net Core 2.0 SignInAsync返回的异常值不能为null,提供程序

C# ASP.Net Core 2.0 SignInAsync返回的异常值不能为null,提供程序,c#,asp.net-core-2.0,nunit-3.0,C#,Asp.net Core 2.0,Nunit 3.0,我有一个ASP.NETCore2.0Web应用程序,我正在用单元测试(使用NUnit)进行改装。应用程序运行良好,到目前为止,大多数测试都运行良好 但是,测试身份验证/授权(用户是否登录并可以访问[Authorize]筛选的操作)失败,原因是 System.ArgumentNullException: Value cannot be null. Parameter name: provider ……在 await HttpContext.SignInAsync(principal); ……但

我有一个ASP.NETCore2.0Web应用程序,我正在用单元测试(使用NUnit)进行改装。应用程序运行良好,到目前为止,大多数测试都运行良好

但是,测试身份验证/授权(用户是否登录并可以访问
[Authorize]
筛选的操作)失败,原因是

System.ArgumentNullException: Value cannot be null.
Parameter name: provider
……在

await HttpContext.SignInAsync(principal);
……但根本原因究竟是什么还不清楚。代码执行在这里被调用的方法中停止,IDE中没有显示异常,但代码执行返回调用方,然后终止(但我仍然看到程序“[13704]dotnet.exe”已退出,代码为0(0x0)。在VS的输出窗口中。)

测试资源管理器显示红色,并给出引用的异常(否则我将不知道该问题)

我正致力于创建一个复制程序,向人们指出(到目前为止,结果有点牵扯其中)

有人知道如何找出根本原因吗?这是否是一个与DI相关的问题(测试中没有提供但正常执行中需要的东西)

更新1:提供请求的身份验证代码

public async Task<IActionResult> Registration(RegistrationViewModel vm) {
    if (ModelState.IsValid) {
        // Create registration for user
        var regData = createRegistrationData(vm);
        _repository.AddUserRegistrationWithGroup(regData);

        var claims = new List<Claim> {
            new Claim(ClaimTypes.NameIdentifier, regData.UserId.ToString())
        };
        var ident = new ClaimsIdentity(claims);
        var principal = new ClaimsPrincipal(ident);

        await HttpContext.SignInAsync(principal); // FAILS HERE

        return RedirectToAction("Welcome", "App");
    } else {
        ModelState.AddModelError("", "Invalid registration information.");
    }

    return View();
}
更新3:基于@nkosi,这是一个问题,源于我没有满足
HttpContext
的依赖注入需求

,但尚不清楚的是:如果这实际上是一个不提供适当的服务依赖性的问题,那么代码为什么正常工作(在未测试时)。SUT(controller)只接受一个dirpository参数(因此在任何情况下都只提供了这个参数)。为什么要创建一个重载的ctor(或mock)只用于测试,而现有的ctor是运行程序时调用的全部,并且运行时没有问题

更新4:虽然@Nkosi用解决方案回答了bug/问题,但我仍然想知道为什么IDE不能准确/一致地呈现底层异常。这是一个bug,还是由于async/await操作符和NUnit测试适配器/运行程序造成的?为什么在调试测试时异常不会像我预期的那样“弹出”,而退出代码仍然为零(通常表示成功返回状态)

这是否是一个与DI相关的问题(测试中没有提供但正常执行中需要的东西)

您正在调用框架将在运行时为您设置的功能。在独立单元测试期间,您需要自己设置这些

控制器的HttpContext缺少用于解析
IAAuthenticationService
IServiceProvider
。该服务实际上是调用
SignInAsync

为了让

await HttpContext.SignInAsync(principal);  // FAILS HERE
…在单元测试期间执行至完成的
注册
操作中,您需要模拟服务提供商,以便
SignInAsync
扩展方法不会失败

更新单元测试安排

//...code removed for brevity

auth.ControllerContext.HttpContext = new DefaultHttpContext() {
    RequestServices = createServiceProviderMock()
};

//...code removed for brevity
其中,
createServiceProviderMock()
是一个用于模拟服务提供者的小方法,该服务提供者将用于填充
HttpContext.RequestServices

public IServiceProvider createServiceProviderMock() {
    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)); //<-- to allow async call to continue

    var serviceProviderMock = new Mock<IServiceProvider>();
    serviceProviderMock
        .Setup(_ => _.GetService(typeof(IAuthenticationService)))
        .Returns(authServiceMock.Object);

    return serviceProviderMock.Object;
}
public IServiceProvider CreateServiceProviderLock(){
var authServiceMock=new Mock();
authServiceMock
.Setup(=>u.SignInAsync(It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny())
.Returns(Task.FromResult((object)null));//.GetService(typeof(IAuthenticationService)))
.Returns(authServiceMock.Object);
返回serviceProviderMock.Object;
}
我还建议模拟
存储库
,以便对该控制器操作进行单独的单元测试,以确保它在没有任何负面影响的情况下完成

目前尚不清楚的是:如果这实际上是一个没有提供适当的服务依赖性的问题,那么为什么代码能够正常工作(在未测试时)。SUT(控制器)只接受
IRepository
参数(因此在任何情况下都只提供了这些参数)。为什么要创建一个重载的ctor(或模拟)以进行测试,而现有的ctor是运行程序时调用的全部,并且运行时没有问题

您在这里混淆了一些东西:首先,您不需要创建单独的构造函数。不用于测试,也不用于将其作为应用程序的一部分实际运行

您应该将控制器的所有直接依赖项定义为构造函数的参数,以便在作为应用程序的一部分运行时,依赖项注入容器将向控制器提供这些依赖项

但这也很重要:在运行应用程序时,有一个依赖项注入容器,负责创建对象并提供所需的依赖项。所以你其实不需要太担心它们来自哪里。但是,当进行单元测试时,情况就不同了。在单元测试中,我们不想使用依赖项注入,因为这只会隐藏依赖项,因此可能会产生与测试冲突的副作用。在单元测试中依赖依赖依赖注入是一个非常好的迹象,表明您不是在进行单元测试,而是在进行集成测试(至少除非您实际测试的是DI容器)

相反,在单元测试中,我们希望显式地创建所有对象,并显式地提供所有依赖项。这意味着我们需要重新设置控制器并传递控制器的所有依赖项。理想情况下,我们使用mock,这样我们就不会在单元测试中依赖外部行为

大多数时候,这都是非常直截了当的。不幸的是,控制器有一些特殊之处:控制器有一个在MVC生命周期中自动提供的
ControllerContext
属性。MVC中的一些其他组件也有类似的功能(例如,
ViewContext
也是自动的
public IServiceProvider createServiceProviderMock() {
    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)); //<-- to allow async call to continue

    var serviceProviderMock = new Mock<IServiceProvider>();
    serviceProviderMock
        .Setup(_ => _.GetService(typeof(IAuthenticationService)))
        .Returns(authServiceMock.Object);

    return serviceProviderMock.Object;
}
context.RequestServices.GetRequiredService<IAuthenticationService>().SignInAsync(context, scheme, principal, properties);
var authenticationServiceMock = new Mock<IAuthenticationService>();
authenticationServiceMock
    .Setup(a => a.SignInAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>()))
    .Returns(Task.CompletedTask);

var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock
    .Setup(s => s.GetService(typeof(IAuthenticationService)))
    .Returns(authenticationServiceMock.Object);
var controller = new AuthController();
controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext()
    {
        RequestServices = serviceProviderMock.Object
    }
};
var urlHelperFactory = new Mock<IUrlHelperFactory>();
serviceProviderMock
    .Setup(s => s.GetService(typeof(IUrlHelperFactory)))
    .Returns(urlHelperFactory.Object);
// mock setup, as above
// …

// arrange
var controller = new AuthController(repositoryMock.Object);
controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext()
    {
        RequestServices = serviceProviderMock.Object
    }
};

var registrationVm = new RegistrationViewModel();

// act
var result = await controller.Registration(registrationVm);

// assert
var redirectResult = result as RedirectToActionResult;
Assert.NotNull(redirectResult);
Assert.Equal("Welcome", redirectResult.ActionName);
Services.AddSingleton<IHttpContextAccessor>(new HttpContextAccessor() { HttpContext = new DefaultHttpContext() { RequestServices = Services.BuildServiceProvider() } });
 public class MyTestBaseClass
 {
  protected ServiceCollection Services { get; set; } = new ServiceCollection();
  MyTestBaseClass
 {

   Services.AddDigiTebFrameworkServices();
        Services.AddDigiTebDBContextService<DigiTebDBContext> 
        (Consts.MainDBConnectionName);
        Services.AddDigiTebIdentityService<User, Role, DigiTebDBContext>();
        Services.AddDigiTebAuthServices();
        Services.AddDigiTebCoreServices();
        Services.AddSingleton<IHttpContextAccessor>(new HttpContextAccessor() { HttpContext = new DefaultHttpContext() { RequestServices = Services.BuildServiceProvider() } });
}
}