Saya memiliki aplikasi ASP.NET MVC Core yang saya tulis untuk pengujian unit. Salah satu metode tindakan menggunakan Nama pengguna untuk beberapa fungsi:
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
yang jelas gagal dalam pengujian unit. Saya melihat sekeliling dan semua saran berasal dari .NET 4.5 untuk meniru HttpContext. Saya yakin ada cara yang lebih baik untuk melakukan itu. Saya mencoba untuk menyuntikkan IPrincipal, tetapi menghasilkan kesalahan; dan saya bahkan mencoba ini (karena putus asa, saya kira):
public IActionResult Index(IPrincipal principal = null) {
IPrincipal user = principal ?? User;
SettingsViewModel svm = _context.MySettings(user.Identity.Name);
return View(svm);
}
tapi ini juga menimbulkan kesalahan. Tidak dapat menemukan apa pun di dokumen juga ...
new Claim(ClaimTypes.Name, "1")
untuk mencocokkan penggunaan pengontroluser.Identity.Name
; tapi selain itu, itulah yang saya coba capai ... Danke schon!User.FindFirstValue(ClaimTypes.NameIdentifier);
untuk mengatur userId pada objek yang saya buat dan gagal karena prinsipal adalah null. Ini memperbaikinya untuk saya. Terima kasih atas jawaban yang bagus!Dalam versi sebelumnya Anda dapat mengatur
User
langsung pada pengontrol, yang dibuat untuk beberapa pengujian unit yang sangat mudah.Jika Anda melihat kode sumber untuk ControllerBase Anda akan melihat bahwa
User
itu diekstrak dariHttpContext
./// <summary> /// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action. /// </summary> public ClaimsPrincipal User => HttpContext?.User;
dan pengontrol mengakses
HttpContext
viaControllerContext
/// <summary> /// Gets the <see cref="Http.HttpContext"/> for the executing action. /// </summary> public HttpContext HttpContext => ControllerContext.HttpContext;
Anda akan melihat bahwa keduanya adalah properti hanya baca. Kabar baiknya adalah bahwa
ControllerContext
properti memungkinkan untuk menetapkan nilainya sehingga akan menjadi jalan Anda.Jadi targetnya adalah mendapatkan objek itu. Dalam Core
HttpContext
abstrak sehingga lebih mudah untuk diolok-olok.Dengan asumsi pengontrol suka
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 }
Menggunakan Moq, tes bisa terlihat seperti ini
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); }
sumber
Ada juga kemungkinan untuk menggunakan kelas yang ada, dan hanya mengejek bila diperlukan.
var user = new Mock<ClaimsPrincipal>(); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = user.Object } };
sumber
Dalam kasus saya, saya perlu memanfaatkan
Request.HttpContext.User.Identity.IsAuthenticated
,Request.HttpContext.User.Identity.Name
dan beberapa logika bisnis berada di luar kontroler. Saya dapat menggunakan kombinasi jawaban Nkosi, Calin's dan Poke untuk ini: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();
sumber
Saya akan menerapkan Pola Pabrik Abstrak.
Buat antarmuka untuk pabrik khusus untuk memberikan nama pengguna.
Kemudian berikan kelas konkret, yang menyediakan
User.Identity.Name
, dan yang memberikan beberapa nilai kode keras lainnya yang berfungsi untuk pengujian Anda.Anda kemudian dapat menggunakan kelas beton yang sesuai bergantung pada produksi versus kode pengujian. Mungkin ingin memasukkan pabrik sebagai parameter, atau beralih ke pabrik yang benar berdasarkan beberapa nilai konfigurasi.
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());
sumber
Saya ingin menekan Controller saya secara langsung dan hanya menggunakan DI seperti AutoFac. Untuk melakukan ini saya mendaftar terlebih dahulu
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);
Selanjutnya saya mengaktifkan injeksi properti saat saya mendaftarkan Pengontrol.
builder.RegisterAssemblyTypes(assembly) .Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();
Kemudian
User.Identity.Name
diisi, dan saya tidak perlu melakukan sesuatu yang khusus saat memanggil metode pada Kontroler saya.public async Task<ActionResult<IEnumerable<Employee>>> Get() { var requestedBy = User.Identity?.Name; ..................
sumber