Asp.net mvc Request.GetOwinContext在单元测试中返回null-如何在单元测试中测试OWIN身份验证?

Asp.net mvc Request.GetOwinContext在单元测试中返回null-如何在单元测试中测试OWIN身份验证?,asp.net-mvc,unit-testing,authentication,asp.net-web-api,owin,Asp.net Mvc,Unit Testing,Authentication,Asp.net Web Api,Owin,我目前正在尝试对一个新的WebAPI项目的身份验证进行单元测试,我正在编写一个使用OWIN进行身份验证的项目,但在单元测试上下文中运行它时遇到问题 这是我的测试方法: [TestMethod] public void TestRegister() { using (WebApp.Start<Startup>("localhost/myAPI")) using (AccountController ac = new AccountController()

我目前正在尝试对一个新的WebAPI项目的身份验证进行单元测试,我正在编写一个使用OWIN进行身份验证的项目,但在单元测试上下文中运行它时遇到问题

这是我的测试方法:

[TestMethod]
public void TestRegister()
{
    using (WebApp.Start<Startup>("localhost/myAPI"))
    using (AccountController ac = new AccountController()
        {
            Request = new System.Net.Http.HttpRequestMessage
                (HttpMethod.Post, "http://localhost/myAPI/api/Account/Register")
        })
    {
        var result = ac.Register(new Models.RegisterBindingModel()
        {
            Email = "testemail@testemail.com",
            Password = "Pass@word1",
            ConfirmPassword = "Pass@word1"
        }).Result;
        Assert.IsNotNull(result);
    }
}
我已通过调试确认正在调用我的
Startup
方法,调用
ConfigurAuth

public void ConfigureAuth(IAppBuilder app)
{
    HttpConfiguration config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    app.UseWebApi(config);

    // Configure the db context and user manager to use a single 
    //  instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>
        (ApplicationUserManager.Create);

    // Enable the application to use a cookie to store information for 
    //  the signed in user
    //  and to use a cookie to temporarily store information about a 
    //  user logging in with a third party login provider
    app.UseCookieAuthentication(new CookieAuthenticationOptions());
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    // Configure the application for OAuth based flow
    PublicClientId = "self";
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/Token"),
        Provider = new ApplicationOAuthProvider(PublicClientId),
        AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
        AllowInsecureHttp = true
    };

    // Enable the application to use bearer tokens to authenticate users
    app.UseOAuthBearerTokens(OAuthOptions);
}
它在以下情况下失败:

return _userManager ?? Request.GetOwinContext()
    .GetUserManager<ApplicationUserManager>();
return\u userManager??Request.GetOwinContext()
.GetUserManager();
使用
NullReferenceException
-
请求。GetOwinContext
正在返回
null


所以我的问题是:我是不是走错路了?我应该只是测试JSON响应吗?或者是否有一种“内部”测试OWIN身份验证的好方法?

您可以在AccountController的构造函数中传入UserManager,这样它就不会试图在OWIN文本中找到它。默认构造函数对单元测试不友好。

我倾向于使用用户管理器工厂注入AccountController。这样,您就可以轻松地交换测试中使用的用户管理器实例。默认工厂可以在构造函数中接受请求,以继续提供用户管理器的每个请求实例。您的测试工厂只返回您希望为测试提供的用户管理器的实例,我通常选择一个实例,它获取一个IUserStore的存根实例,这样就不会对用于存储身份信息的后端产生硬依赖

工厂接口和类:

public interface IUserManagerFactory<TUser>
    where TUser : class, global::Microsoft.AspNet.Identity.IUser<string>
{
    UserManager<TUser> Create();
}


public class UserManagerFactory : IUserManagerFactory<AppUser>
{
    private HttpRequestMessage request;

    public UserManagerFactory(HttpRequestMessage request)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        this.request = request;
    }

    public UserManager<AppUser, string> Create()
    {
        return request.GetOwinContext().GetUserManager<UserManager<AppUser>>();
    }
}
公共接口IUserManagerFactory 其中TUser:class,global::Microsoft.AspNet.Identity.IUser { UserManager创建(); } 公共类UserManagerFactory:IUserManagerFactory { 私有HttpRequestMessage请求; 公共用户管理器工厂(HttpRequestMessage请求) { if(请求==null) { 抛出新的ArgumentNullException(“请求”); } this.request=请求; } 公共用户管理器创建() { 返回请求.GetOwinContext().GetUserManager(); } } 会计控制员:

public AccountController(IUserManagerFactory<AppUser> userManagerFactory)
{
    this.userManagerFactory = userManagerFactory;
}

private UserManager<AppUser> userManager;

public UserManager<AppUser> UserManager
{
    get
    {
        if (this.userManager == null)
        {
            this.userManager = this.userManagerFactory.Create(); 
        }

        return this.userManager;
    }
}
公共帐户控制器(IUserManagerFactory用户管理器工厂) { this.userManagerFactory=userManagerFactory; } 私有用户管理器用户管理器; 公共用户管理器用户管理器 { 得到 { if(this.userManager==null) { this.userManager=this.userManagerFactory.Create(); } 返回这个.userManager; } } 测试工厂:

public class TestUserManagerFactory : IUserManagerFactory<AppUser>
{
    private IUserStore<AppUser> userStore;

    public TestUserManagerFactory()
    {
        this.userStore = new MockUserStore();
    }

    public UserManager<AppUser> Create()
    { 
        return new UserManager<AppUser>(new MockUserStore());
    }
}
公共类TestUserManagerFactory:IUserManagerFactory
{
私有IUserStore用户存储;
公共TestUserManagerFactory()
{
this.userStore=new MockUserStore();
}
公共用户管理器创建()
{ 
返回新的UserManager(newmockuserstore());
}
}

要确保在测试期间OWIN上下文可用(即,在调用
Request.GetOwinContext()
时修复空引用异常),您需要在测试项目中安装
Microsoft.AspNet.WebApi.OWIN
NuGet包。安装后,您可以根据请求使用
SetOwinContext
扩展方法

例如:

var controller = new MyController();
controller.Request = new HttpRequestMessage(HttpMethod.Post,
    new Uri("api/data/validate", UriKind.Relative)
    );
controller.Request.SetOwinContext(new OwinContext());


话虽如此,我同意针对特定用例的其他答案——在构造函数中提供AppApplicationUserManager实例或工厂。如果您需要直接与测试将使用的上下文进行交互,则必须执行上面的
SetOwinContext
步骤。

GetOwinContext调用context.getowinconvironment(); 那是

私有静态IDictionary GetowinenEnvironment(此HttpContextBase上下文)
{
返回(IDictionary)context.Items[HttpContextItemKeys.OwinEnvironmentKey];
}
HttpContextItemKeys.owinenEnvironmentKey是一个常量“owin.Environment” 因此,如果您将其添加到httpcontext的项中,它将起作用

var request = new HttpRequest("", "http://google.com", "rUrl=http://www.google.com")
    {
        ContentEncoding = Encoding.UTF8  //UrlDecode needs this to be set
    };

    var ctx = new HttpContext(request, new HttpResponse(new StringWriter()));

    //Session need to be set
    var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
        new HttpStaticObjectsCollection(), 10, true,
        HttpCookieMode.AutoDetect,
        SessionStateMode.InProc, false);
    //this adds aspnet session
    ctx.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Instance,
        null, CallingConventions.Standard,
        new[] { typeof(HttpSessionStateContainer) },
        null)
        .Invoke(new object[] { sessionContainer });

    var data = new Dictionary<string, object>()
    {
        {"a", "b"} // fake whatever  you need here.
    };

    ctx.Items["owin.Environment"] = data;
var请求=新的HttpRequest(“,”http://google.com“,”鲁尔=http://www.google.com")
{
ContentEncoding=Encoding.UTF8//UrlDecode需要设置此选项
};
var ctx=新的HttpContext(请求,新的HttpResponse(新的StringWriter());
//需要设置会话
var sessionContainer=new HttpSessionStateContainer(“id”,new SessionStateItemCollection(),
新的HttpStaticObjectsCollection(),10,true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,false);
//这将添加aspnet会话
ctx.Items[“AspSession”]=typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
空,调用约定。标准,
新[]{typeof(HttpSessionStateContainer)},
空)
.Invoke(新对象[]{sessionContainer});
var data=newdictionary()
{
{“a”,“b”}//在这里伪造你需要的任何东西。
};
ctx.Items[“owin.Environment”]=数据;
var data=new Dictionary()
{
{“a”,“b”}//在这里伪造你需要的任何东西。
};
ctx.Items[“owin.Environment”]=数据;

使用这段代码并将其添加到HttpContext而不是ctx,单元测试非常有效。

这里的答案很有帮助,但并没有让我完全理解,下面是一个完整的示例:

var userStore = new Mock<IUserStore<User>>();
var appUserMgrMock = new Mock<ApplicationUserManager>(userStore.Object);

var owin = new OwinContext();
owin.Set(appUserMgrMock.Object);

HttpContext.Current = new HttpContext(new HttpRequest(null, "http://test.com", null), new HttpResponse(null));
HttpContext.Current.Items["owin.Environment"] = owin.Environment;
var userStore=new Mock();
var appUserMgrMock=new Mock(userStore.Object);
var owin=new OwinContext();
Set(appUserMgrMock.Object);
当前=新的HttpContext(新的HttpRequest(null)http://test.com“,null),新的HttpResponse(null));
HttpContext.Current.Items[“owin.Environment”]=owin.Environment;

请记住安装所有必需的nuget软件包

为文字墙道歉-我想提供尽可能多的上下文:遗憾的是,没有一个答案是真正的答案,而不是解决办法。我不想嘲笑它,我想实际测试它。哦,好吧。你觉得用户经理怎么样?在本单元的上下文中,您是如何发现这一点的
var controller = new MyController();
controller.Request = new HttpRequestMessage(HttpMethod.Post,
    new Uri("api/data/validate", UriKind.Relative)
    );
controller.Request.SetOwinContext(new OwinContext());
  private static IDictionary<string, object> GetOwinEnvironment(this HttpContextBase context)
    {
        return (IDictionary<string, object>) context.Items[HttpContextItemKeys.OwinEnvironmentKey];
    }
var request = new HttpRequest("", "http://google.com", "rUrl=http://www.google.com")
    {
        ContentEncoding = Encoding.UTF8  //UrlDecode needs this to be set
    };

    var ctx = new HttpContext(request, new HttpResponse(new StringWriter()));

    //Session need to be set
    var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
        new HttpStaticObjectsCollection(), 10, true,
        HttpCookieMode.AutoDetect,
        SessionStateMode.InProc, false);
    //this adds aspnet session
    ctx.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Instance,
        null, CallingConventions.Standard,
        new[] { typeof(HttpSessionStateContainer) },
        null)
        .Invoke(new object[] { sessionContainer });

    var data = new Dictionary<string, object>()
    {
        {"a", "b"} // fake whatever  you need here.
    };

    ctx.Items["owin.Environment"] = data;
var data = new Dictionary<string, object>()
{
    {"a", "b"} // fake whatever  you need here.
};

ctx.Items["owin.Environment"] = data;
var userStore = new Mock<IUserStore<User>>();
var appUserMgrMock = new Mock<ApplicationUserManager>(userStore.Object);

var owin = new OwinContext();
owin.Set(appUserMgrMock.Object);

HttpContext.Current = new HttpContext(new HttpRequest(null, "http://test.com", null), new HttpResponse(null));
HttpContext.Current.Items["owin.Environment"] = owin.Environment;