ユニットテストを記述しているASP.NET MVCコアアプリケーションがあります。アクションメソッドの1つは、いくつかの機能にユーザー名を使用します。
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
ユニットテストでは明らかに失敗します。私は周りを見回しましたが、すべての提案は.NET 4.5からHttpContextのモックまでです。私はそれをするより良い方法があると確信しています。 IPrincipalを注入しようとしましたが、エラーがスローされました。そして、私もこれを試してみました(絶望から、私は思う):
public IActionResult Index(IPrincipal principal = null) {
IPrincipal user = principal ?? User;
SettingsViewModel svm = _context.MySettings(user.Identity.Name);
return View(svm);
}
しかし、これもエラーを投げました。ドキュメントにも何も見つかりませんでした...
コントローラーの User
アクセス を介して HttpContext
コントローラーの 。後者は 格納されているControllerContext
内にあります。
ユーザーを設定する最も簡単な方法は、構築されたユーザーに異なるHttpContextを割り当てることです。この目的のために DefaultHttpContext
を使用できます。そのように、すべてをモックする必要はありません。次に、コントローラーコンテキスト内でそのHttpContextを使用し、コントローラーインスタンスに渡します。
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, "example name"),
new Claim(ClaimTypes.NameIdentifier, "1"),
new Claim("custom-claim", "example claim value"),
}, "mock"));
var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext() { User = user }
};
独自の ClaimsIdentity
を作成するときは、明示的に authenticationType
をコンストラクタに渡すようにしてください。これにより、 IsAuthenticated
が正しく機能するようになります(コードでそれを使用して、ユーザーが認証されているかどうかを判断する場合)。
以前のバージョンでは、User
をコントローラーに直接設定できたため、非常に簡単な単体テストができました。
ControllerBase のソースコードを見ると、User
がHttpContext
から抽出されていることがわかります。
/// <summary>
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User
{
get
{
return HttpContext?.User;
}
}
コントローラーはHttpContext
を介してControllerContext
にアクセスします
/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext
{
get
{
return ControllerContext.HttpContext;
}
}
これら2つは読み取り専用プロパティであることに気付くでしょう。幸いなことに、ControllerContext
プロパティで値を設定できるので、それがあなたの方法になります。
したがって、ターゲットはそのオブジェクトに到達することです。 CoreのHttpContext
は抽象的であるため、モックがはるかに簡単です。
のようなコントローラを想定
public class MyController : Controller {
IMyContext _context;
public MyController(IMyContext context) {
_context = context;
}
public IActionResult Index() {
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
return View(svm);
}
//...other code removed for brevity
}
Moqを使用すると、テストは次のようになります
public void Given_User_Index_Should_Return_ViewResult_With_Model() {
//Arrange
var username = "FakeUserName";
var identity = new GenericIdentity(username, "");
var mockPrincipal = new Mock<IPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
var model = new SettingsViewModel() {
//...other code removed for brevity
};
var mockContext = new Mock<IMyContext>();
mockContext.Setup(m => m.MySettings(username)).Returns(model);
var controller = new MyController(mockContext.Object) {
ControllerContext = new ControllerContext {
HttpContext = mockHttpContext.Object
}
};
//Act
var viewResult = controller.Index() as ViewResult;
//Assert
Assert.IsNotNull(viewResult);
Assert.IsNotNull(viewResult.Model);
Assert.AreEqual(model, viewResult.Model);
}
私は抽象ファクトリーパターンを実装したいと考えています。
特にユーザー名を提供するためのファクトリー用のインターフェースを作成します。
次に、具体的なクラスを提供します。1つはUser.Identity.Name
を提供し、もう1つはテストに役立つその他のハードコードされた値を提供します。
その後、本番コードとテストコードに応じて適切な具体的なクラスを使用できます。おそらく、ファクトリーをパラメーターとして渡すか、何らかの構成値に基づいて正しいファクトリーに切り替えることを検討しています。
interface IUserNameFactory
{
string BuildUserName();
}
class ProductionFactory : IUserNameFactory
{
public BuildUserName() { return User.Identity.Name; }
}
class MockFactory : IUserNameFactory
{
public BuildUserName() { return "James"; }
}
IUserNameFactory factory;
if(inProductionMode)
{
factory = new ProductionFactory();
}
else
{
factory = new MockFactory();
}
SettingsViewModel svm = _context.MySettings(factory.BuildUserName());
また、既存のクラスを使用し、必要な場合にのみモックする可能性もあります。
var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = user.Object
}
};
私の場合、Request.HttpContext.User.Identity.IsAuthenticated
、Request.HttpContext.User.Identity.Name
、およびコントローラーの外側にあるいくつかのビジネスロジックを使用する必要がありました。これには、Nkosi、Calin、Pokeの回答を組み合わせて使用することができました。
var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);
var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();
var controller = new MyController(...);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
User = mockPrincipal.Object
};
var result = controller.Get() as OkObjectResult;
//Assert results
mockAuthHandler.Verify();
IHttpContextAccessorを使用して、Net CoreでHttpContextをモックできます。次のようになります。
public class UserRepository : IUserRepository
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserRepository(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void LogCurrentUser()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
service.LogAccessRequest(username);
}
}
これは、このページから取得されます: https://docs.Microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-2.2