web-dev-qa-db-ja.com

Request.GetOwinContextが単体テスト内でnullを返します-単体テスト内でOWIN認証をテストするにはどうすればよいですか?

現在、OWINを使用して認証を記述している新しいWebAPIプロジェクトの認証を単体テストしようとしていますが、単体テストコンテキストでの実行に問題があります。

これは私のテスト方法です:

[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 = "[email protected]",
            Password = "Pass@Word1",
            ConfirmPassword = "Pass@Word1"
        }).Result;
        Assert.IsNotNull(result);
    }
}

次の内部例外を除いて、.Resultの取得時にAggregateExceptionを取得しています。

Result Message: 
Test method myAPI.Tests.Controllers.AccountControllerTest.TestRegister 
    threw exception: 
System.ArgumentNullException: Value cannot be null.
Parameter name: context
Result StackTrace:  
at Microsoft.AspNet.Identity.Owin.OwinContextExtensions
    .GetUserManager[TManager](IOwinContext context)
at myAPI.Controllers.AccountController.get_UserManager()
...

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);
}

私はいくつかのことを試しましたが、何も機能しないようです-OWINコンテキストを取得することはできません。テストは次のコードで失敗しています:

// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = new ApplicationUser() 
       { UserName = model.Email, Email = model.Email };

    IdentityResult result = await UserManager.CreateAsync(user, model.Password);

    if (!result.Succeeded)
    {
        return GetErrorResult(result);
    }

    return Ok();
}

これはUserManagerプロパティを呼び出します:

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? Request.GetOwinContext()
           .GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

それは失敗します:

return _userManager ?? Request.GetOwinContext()
    .GetUserManager<ApplicationUserManager>();

NullReferenceException-Request.GetOwinContextnullを返します。

だから私の質問は:私はこれが間違っているのですか? JSON応答をテストするだけですか?または、OWIN認証を「内部で」テストする良い方法はありますか?

19
Codeman

GetOwinContextはcontext.GetOwinEnvironment();を呼び出します。それは

  private static IDictionary<string, object> GetOwinEnvironment(this HttpContextBase context)
    {
        return (IDictionary<string, object>) context.Items[HttpContextItemKeys.OwinEnvironmentKey];
    }

そしてHttpContextItemKeys.OwinEnvironmentKeyは定数 "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;
13
Birey

テスト中にOWINコンテキストが利用可能であることを確認するには(つまり、Request.GetOwinContext()を呼び出すときのnull参照例外を修正するには)、テストプロジェクト内に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());

https://msdn.Microsoft.com/en-us/library/system.net.http.owinhttprequestmessageextensions.setowincontext%28v=vs.118%29.aspx を参照してください

そうは言っても、私はあなたの特定のユースケースの他の答えに同意します-コンストラクターでAppplicationUserManagerインスタンスまたはファクトリーを提供します。上記のSetOwinContext手順は、テストで使用するコンテキストと直接対話する必要がある場合に必要です。

9
Gnosian

AccountControllerのコンストラクターでUserManagerを渡すだけでよいので、owinContextでそれを見つけようとしません。デフォルトのコンストラクターは、単体テストには適していません。

2
Hao Kung

私がしがちなことは、AccountManagerにユーザーマネージャファクトリを注入することです。これにより、テストで使用されるユーザーマネージャーのインスタンスを簡単に交換できます。デフォルトのファクトリは、リクエストをコンストラクターで受け取り、ユーザーマネージャーのリクエストごとのインスタンスを提供し続けることができます。テストファクトリは、テストを提供するユーザーマネージャーのインスタンスを返すだけです。通常、IUserStoreのスタブ化されたインスタンスを取得するため、ID情報を格納するために使用されるバックエンドに大きな依存関係はありません。

ファクトリインターフェースとクラス:

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>>();
    }
}

AccountController:

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;
    }
}

テスト工場:

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());
    }
}
1
Tom
var data = new Dictionary<string, object>()
{
    {"a", "b"} // fake whatever  you need here.
};

ctx.Items["owin.Environment"] = data;

このコードを使用し、ctxの代わりにHttpContextに追加すると、単体テストは魅力的に機能しました。

1

ここでの答えは役に立ちましたが、私をそこまで完全に導きませんでした、ここに完全な例があります:

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;

必要なすべてのnugetパッケージをインストールすることを忘れないでください!

0
Shahin Dohan