現在、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.GetOwinContext
はnull
を返します。
だから私の質問は:私はこれが間違っているのですか? JSON応答をテストするだけですか?または、OWIN認証を「内部で」テストする良い方法はありますか?
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;
テスト中に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());
そうは言っても、私はあなたの特定のユースケースの他の答えに同意します-コンストラクターでAppplicationUserManagerインスタンスまたはファクトリーを提供します。上記のSetOwinContext
手順は、テストで使用するコンテキストと直接対話する必要がある場合に必要です。
AccountControllerのコンストラクターでUserManagerを渡すだけでよいので、owinContextでそれを見つけようとしません。デフォルトのコンストラクターは、単体テストには適していません。
私がしがちなことは、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());
}
}
var data = new Dictionary<string, object>()
{
{"a", "b"} // fake whatever you need here.
};
ctx.Items["owin.Environment"] = data;
このコードを使用し、ctxの代わりにHttpContextに追加すると、単体テストは魅力的に機能しました。
ここでの答えは役に立ちましたが、私をそこまで完全に導きませんでした、ここに完全な例があります:
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パッケージをインストールすることを忘れないでください!