J'ai une application ASP.NET MVC Core pour laquelle j'écris des tests unitaires. L'une des méthodes d'action utilise le nom d'utilisateur pour certaines fonctionnalités:
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
qui échoue évidemment dans le test unitaire. J'ai regardé autour de moi et toutes les suggestions proviennent de .NET 4.5 pour simuler HttpContext. Je suis sûr qu'il existe une meilleure façon de faire cela. J'ai essayé d'injecter IPrincipal, mais cela a généré une erreur; et j'ai même essayé ceci (par désespoir, je suppose):
public IActionResult Index(IPrincipal principal = null) {
IPrincipal user = principal ?? User;
SettingsViewModel svm = _context.MySettings(user.Identity.Name);
return View(svm);
}
mais cela a également jeté une erreur. Je n'ai rien trouvé dans la documentation non plus ...
new Claim(ClaimTypes.Name, "1")
pour correspondre à l'utilisation du contrôleuruser.Identity.Name
; mais sinon c'est exactement ce que j'essayais de réaliser ... Danke schon!User.FindFirstValue(ClaimTypes.NameIdentifier);
pour définir userId sur un objet que je créais et échouait car le principal était nul. Cela a résolu cela pour moi. Merci pour la bonne réponse!Dans les versions précédentes, vous auriez pu définir
User
directement sur le contrôleur, ce qui permettait des tests unitaires très faciles.Si vous regardez le code source de ControllerBase, vous remarquerez que le
User
est extrait deHttpContext
./// <summary> /// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action. /// </summary> public ClaimsPrincipal User => HttpContext?.User;
et le contrôleur accède au
HttpContext
viaControllerContext
/// <summary> /// Gets the <see cref="Http.HttpContext"/> for the executing action. /// </summary> public HttpContext HttpContext => ControllerContext.HttpContext;
Vous remarquerez que ces deux propriétés sont en lecture seule. La bonne nouvelle est que la
ControllerContext
propriété permet de définir sa valeur afin que ce soit votre chemin.Donc, l'objectif est d'atteindre cet objet. In Core
HttpContext
est abstrait, il est donc beaucoup plus facile de se moquer.En supposant un contrôleur comme
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 }
En utilisant Moq, un test pourrait ressembler à ceci
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); }
la source
Il est également possible d'utiliser les classes existantes et de se moquer uniquement lorsque cela est nécessaire.
var user = new Mock<ClaimsPrincipal>(); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = user.Object } };
la source
Dans mon cas, je devais utiliser
Request.HttpContext.User.Identity.IsAuthenticated
,Request.HttpContext.User.Identity.Name
et une logique d'affaires assis à l' extérieur du contrôleur. J'ai pu utiliser une combinaison des réponses de Nkosi, Calin et Poke pour cela: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();
la source
Je chercherais à implémenter un modèle de fabrique abstraite.
Créez une interface pour une fabrique spécifiquement pour fournir des noms d'utilisateur.
Ensuite, fournissez des classes concrètes, une qui fournit
User.Identity.Name
et une qui fournit une autre valeur codée en dur qui fonctionne pour vos tests.Vous pouvez ensuite utiliser la classe concrète appropriée en fonction de la production par rapport au code de test. Vous cherchez peut-être à transmettre l'usine en tant que paramètre ou à passer à l'usine appropriée en fonction d'une valeur de configuration.
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());
la source
Je veux frapper directement mes contrôleurs et utiliser simplement DI comme AutoFac. Pour ce faire, je m'inscris d'abord
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);
Ensuite, j'active l'injection de propriété lorsque j'enregistre les contrôleurs.
builder.RegisterAssemblyTypes(assembly) .Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();
Puis
User.Identity.Name
est rempli et je n'ai rien de spécial à faire lors de l'appel d'une méthode sur mon Controller.public async Task<ActionResult<IEnumerable<Employee>>> Get() { var requestedBy = User.Identity?.Name; ..................
la source