У меня есть приложение ASP.NET MVC Core, для которого я пишу модульные тесты. Один из методов действия использует имя пользователя для некоторых функций:
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);
}
но это тоже привело к ошибке. В документации тоже ничего не нашел ...
new Claim(ClaimTypes.Name, "1")
было соответствовать использованию контроллераuser.Identity.Name
; но в остальном это именно то, чего я пытался достичь ... Danke schon!User.FindFirstValue(ClaimTypes.NameIdentifier);
чтобы установить userId для объекта, который я создавал, и не работал, потому что принципал был нулевым. Это исправило это для меня. Спасибо за отличный ответ!В предыдущих версиях вы могли установить
User
непосредственно на контроллере, что упростило выполнение некоторых модульных тестов.Если вы посмотрите исходный код ControllerBase, вы заметите, что
User
файл извлечен изHttpContext
./// <summary> /// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action. /// </summary> public ClaimsPrincipal User => HttpContext?.User;
и контроллер получает доступ к переходному
HttpContext
отверстиюControllerContext
/// <summary> /// Gets the <see cref="Http.HttpContext"/> for the executing action. /// </summary> public HttpContext HttpContext => ControllerContext.HttpContext;
Вы заметите, что эти два свойства доступны только для чтения. Хорошая новость заключается в том, что
ControllerContext
свойство позволяет установить его значение, чтобы оно было вашим путем.Итак, цель - добраться до этого объекта. In 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<ClaimsPrincipal>(); 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); }
источник
Также есть возможность использовать существующие классы и имитировать только при необходимости.
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();
источник
Я бы хотел реализовать шаблон абстрактной фабрики.
Создайте интерфейс для фабрики специально для предоставления имен пользователей.
Затем укажите конкретные классы, один из которых обеспечивает
User.Identity.Name
, а другой - другое жестко закодированное значение, которое подходит для ваших тестов.Затем вы можете использовать соответствующий конкретный класс в зависимости от производственного или тестового кода. Возможно, вы хотите передать фабрику в качестве параметра или переключиться на правильную фабрику на основе некоторого значения конфигурации.
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());
источник
Я хочу напрямую поразить свои контроллеры и просто использовать DI, например AutoFac. Для этого я сначала регистрируюсь
ContextController
.var identity = new GenericIdentity("Test User"); var httpContext = new DefaultHttpContext() { User = new GenericPrincipal(identity, null) }; var context = new ControllerContext { HttpContext = httpContext}; builder.RegisterInstance(context);
Затем я включаю внедрение свойств при регистрации контроллеров.
builder.RegisterAssemblyTypes(assembly) .Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();
Затем
User.Identity.Name
заполняется, и мне не нужно делать ничего особенного при вызове метода в моем контроллере.public async Task<ActionResult<IEnumerable<Employee>>> Get() { var requestedBy = User.Identity?.Name; ..................
источник