I have this method:
[HttpPut]
[AuthorizeClaim("field:write")]
[ApiConventionMethod(typeof(AttemptApiConventions), nameof(AttemptApiConventions.AttemptPut))]
public override async Task<ActionResult<Field>> UpdateAsync(Field model)
{
var getAttempt = await Mediator.Send(new GenericGet<Field>(model.Id));
if (getAttempt.Failure) return Ok(getAttempt);
var field = getAttempt.Result;
if (!field.IsSpecification && !User.IsInRole("Administrator"))
return Ok(new ForbiddenError(string.Format(Resources.PermissionError, "this field")).ToAttempt<Field>());
if (!field.IsSpecification && model.Name != field.Name)
return Ok(new ValidationError(string.Format(Resources.CannotBeChanged, nameof(Field.Name))).ToAttempt<Field>());
return await base.UpdateAsync(model);
}
As you can see, I check if the User.IsInRole to display a different error message when they are trying to change a field.
The problem is, I have this unit test:
[Test]
public async Task ReturnBadRequestIfSpecificationFieldNameChanges()
{
// Assemble
var model = new Field {Id = 1, IsSpecification = false, Name = "Test"};
var services = FieldsControllerContext.GivenServices();
var controller = services.WhenCreateController("Administrator");
services.Mediator.Send(Arg.Any<GenericGet<Field>>())
.Returns(new Field {Id = 1, IsSpecification = false, Name = "Old"});
// Act
var actionResult = await controller.UpdateAsync(model);
// Assert
actionResult.ShouldBeBadRequest(string.Format(Sxp.Web.Properties.Resources.CannotBeChanged, nameof(Field.Name)));
}
Take a look at the line services.WhenCreateController("Administrator");. It is passing the role I want the mocked user to be a member of. This is set like this:
public static class ControllerExtensions
{
public static T WithUser<T>(this T controller, string roles = null) where T: ControllerBase
{
var id = Guid.NewGuid().ToString();
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "example name"),
new Claim(ClaimTypes.NameIdentifier, id),
new Claim(JwtClaimTypes.Subject, id),
};
if (!string.IsNullOrEmpty(roles))
{
var r = roles.Split(",");
claims.AddRange(r.Select(role => new Claim(JwtClaimTypes.Role, role)));
}
var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "mock"));
controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = user
}
};
return controller;
}
}
When I debug my test, I can see this:
Can anyone tell me why?
I found the answer. This line was to blame:
claims.AddRange(r.Select(role => new Claim(JwtClaimTypes.Role, role)));
It isn't JwtClaimTypes that it checks, it actually checks for:
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
Related
I would like to unit test this Controller. It returns the name of the role and the count of users that belong to that role:
[HttpGet]
[EnableQuery]
public IEnumerable<RoleDto> Get()
{
var roles = dbContext.Roles.ToList();
return roles.Select(r =>
{
var dto = mapper.Map<RoleDto>(r);
dto.UserCount = dbContext.UserRoles.Count(x => x.RoleId == r.Id);
return dto;
}).ToList();
}
And here is my test - The DTO is always null and later throws an exception. I am unable to map this properly.
It is a simple unit test that creates a user, creates roles and add the user to those roles.
It then checks if a user has been added to that role.
[Test]
public async Task Get_Should_Return_List_OfRoles()
{
TestSetup();
var mapperMock = new Mock<IMapper>();
IdentityRole role = null;
mapperMock.Setup(u => u.Map<RoleDto>(It.IsAny<IdentityRole>()))
.Callback<object>(inputargs => role = inputargs as IdentityRole);
var roleManagerWrapperMock = new Mock<IRoleManagerWrapper>();
var admin = new IntentUser
{
UserName = "Administrator2",
Email = "admin2#intent2.app",
EmailConfirmed = true,
IsEnabled = true,
IsSystemUser = true
};
var adminRole = new IdentityRole()
{
Name = "Admin"
};
var managerRole = new IdentityRole()
{
Name = "Manager"
};
ApplicationDbContext.Users.Add(admin);
ApplicationDbContext.Roles.Add(managerRole);
ApplicationDbContext.Roles.Add(adminRole);
ApplicationDbContext.SaveChanges();
var adminRoleAdded = new IdentityUserRole<string>()
{
RoleId = adminRole.Id,
UserId = admin.Id
};
var managerRoleAdded = new IdentityUserRole<string>()
{
RoleId = managerRole.Id,
UserId = admin.Id
};
ApplicationDbContext.UserRoles.Add(adminRoleAdded);
ApplicationDbContext.UserRoles.Add(managerRoleAdded);
ApplicationDbContext.SaveChanges();
var sut = new RolesController(roleManagerWrapperMock.Object, ApplicationDbContext, mapperMock.Object);
var result = sut.Get();
foreach (var roleDto in result)
{
Assert.AreEqual(roleDto.UserCount, 1);
}
}
protected void TestSetup(string databaseName = null)
{
if (databaseName == null)
{
databaseName = GetTestName();
}
TestCleanup();
ServiceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName)
.UseInternalServiceProvider(ServiceProvider)
.Options;
ApplicationDbContext = new ApplicationDbContext(dbContextOptions);
}
As mentioned in the comments, you can use an actual mapper instead of trying to mock it.
//...
//Configure mapping just for this test but something like this
//should be in accessible from your composition root and called here.
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<RoleDto, IdentityRole>();
cfg.CreateMap<IdentityRole, RoleDto>();
});
IMapper mapper = config.CreateMapper();
// or
//IMapper mapper = new Mapper(config);
//...
Reference Automapper: Getting Started Guide
I am writing unit tests and this mapper is causing me grief again. I understood from a previous post that I cannot Mock the mapper, i have to use it straight away. So I have created maps but it says missing type map configuration.
public RoleDto GetSingle([FromRoute] string id)
{
var r = roleManagerWrapper.GetSingleRole(id);
return mapper.Map<RoleDto>(r);
}
It breaks when it tries to map the object. Is there any special mapping for Task <IdentityRole> that needs to be implemented?
public async Task<IdentityRole> GetSingleRole(string roleId)
{
var role = await this.roleManager.Roles.SingleOrDefaultAsync(r => r.Id == roleId);
return role;
}
Here is my test that only counts the number of roles that are created.
[Test]
public async Task Get_Single()
{
TestSetup();
var roleManagerWrapperMock = new Mock<IRoleManagerWrapper>();
var adminRole = new IdentityRole()
{
Name = "Admin",
Id = "4a8de423-5663-4831-ac07-7ce92465b008"
};
var managerRole = new IdentityRole()
{
Name = "Manager",
Id = "40f74162-3359-4253-9b5a-ad795b328267"
};
ApplicationDbContext.Roles.Add(managerRole);
ApplicationDbContext.Roles.Add(adminRole);
ApplicationDbContext.SaveChanges();
var sut = new RolesController(roleManagerWrapperMock.Object, ApplicationDbContext, Mapper);
var result = sut.GetSingle("4a8de423-5663-4831-ac07-7ce92465b008");
Assert.AreEqual(result.UserCount, 1);
}
protected void TestSetup(string databaseName = null)
{
if (databaseName == null)
{
databaseName = GetTestName();
}
TestCleanup();
ServiceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName)
.UseInternalServiceProvider(ServiceProvider)
.Options;
ApplicationDbContext = new ApplicationDbContext(dbContextOptions);
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<RoleDto, IdentityRole>();
cfg.CreateMap<IdentityRole, RoleDto>();
cfg.CreateMap<CreateRoleDto, IdentityRole>().ReverseMap();
cfg.CreateMap<UpdateRoleDto, IdentityRole>().ReverseMap();
});
Mapper = config.CreateMapper();
}
The action needs to be refactored to use proper asyn syntax since GetSingleRole returns Task<IdentityRole>
public Task<RoleDto> GetSingle([FromRoute] string id) {
IdentityRole r = await roleManagerWrapper.GetSingleRole(id);
return mapper.Map<RoleDto>(r);
}
And the test updated accordingly
[Test]
public async Task Get_Single() {
//Arrange
TestSetup();
var roleManagerWrapperMock = new Mock<IRoleManagerWrapper>();
//...omitted for brevity
var sut = new RolesController(roleManagerWrapperMock.Object, ApplicationDbContext, Mapper);
//Act
RoleDto result = await sut.GetSingle("4a8de423-5663-4831-ac07-7ce92465b008");
//Assert
Assert.AreEqual(result.UserCount, 1);
}
I have an issue regarding writing a unit test for a Razor Page combined with MVVM. I want to test an OnPostAsync() method from Razor, however this method will call two more methods which require a ClaimsPrincipal User. I can't give the User as a parameter for these methods from within my test however.
My current test method:
[Test]
public void MakeBookingTest()
{
//Arrange
var optionsbuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsbuilder.UseInMemoryDatabase(databaseName: "TeacherDB");
var _dbContext = new ApplicationDbContext(optionsbuilder.Options);
JsonResult json = new JsonResult(true);
_dbContext.ImportOption.Add(new ImportOption { Id = 1, isUnique = 1, Option = "Teacher" });
_dbContext.SaveChanges();
var mockedUserManager = GetMockUserManager();
var mockedCalendar = new Mock<ICalendarService>();
Booking booking = new Booking {
EventId = "2",
ClassroomId = 3,
BeginTime = DateTime.Now,
EndTime = DateTime.Now,
Description = "fjdkafjal",
Summary = "Test01" };
mockedCalendar.Setup(x => x.CreateEvent(booking, "2", "0863629#hr.nl", false)).Returns("111");
var mockedRoleManager = GetMockRoleManager();
var model = new MakeBookingModel(_dbContext, mockedUserManager.Object, mockedCalendar.Object, mockedRoleManager.Object);
//var controllerContext = new Mock<ControllerContext>();
var result = model.OnPostAsync();
}
The method which I want to test:
[ValidateAntiForgeryToken]
public async Task<IActionResult> OnPostAsync()
{
CurrentRole = await _validation.GetCurrentRole(User);
CurrentUser = await _userManager.GetUserAsync(User);
//(rest of the code...)
_validation.GetCurrentRole method:
public async Task<string> GetCurrentRole(ClaimsPrincipal User)
{
ApplicationUser currentUser = await _userManager.GetUserAsync(User);
Task<IList<string>> rolesUser = _userManager.GetRolesAsync(currentUser);
return rolesUser.Result.First();
}
I can't seem to make User in the OnPostAsync/GetCurrentRole to not be null.
How would I assign User to those methods?
Set the User via the HTTP context which is part of the PageModel's PageContext
// Arrange
//...code removed for brevity
//Create test user
var displayName = "User name";
var identity = new GenericIdentity(displayName);
var principle = new ClaimsPrincipal(identity);
// use default context with user
var httpContext = new DefaultHttpContext() {
User = principle
}
//need these as well for the page context
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
// need page context for the page model
var pageContext = new PageContext(actionContext) {
ViewData = viewData
};
//create model with necessary dependencies
var model = new MakeBookingModel(_dbContext, mockedUserManager.Object, mockedCalendar.Object, mockedRoleManager.Object) {
PageContext = pageContext
};
//Act
//...
Reference Razor Pages unit tests in ASP.NET Core
You are mixing async and blocking calls like .Result which is causing a deadlock in GetCurrentRole.
Refactor to be async all the way through
public async Task<string> GetCurrentRole(ClaimsPrincipal User) {
ApplicationUser currentUser = await _userManager.GetUserAsync(User);
var rolesUser = await _userManager.GetRolesAsync(currentUser);
return rolesUser.FirstOrDefault();
}
I am trying to run unit tests for a successfull login by verifying that the AuthenticationManager SignIn method has been called once Here's the error I am getting:
Message: Test method Portfolio.UnitTest.WebUI.Controllers.LoginControllerTests.Login_Verified_Authenticates_System_User threw exception:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: m => m.SignIn(AuthenticationProperties, [ClaimsIdentity])
Configured setups:
m => m.SignIn(AuthenticationProperties, [ClaimsIdentity])
Performed invocations:
IAuthenticationManager.SignIn(AuthenticationProperties, [ClaimsIdentity])
Even the error message seems to be contradicting itself.
My controller class/method:
public class LoginController : ProjectBaseController
{
private IAuthenticationManager _authenticationManager;
public IAuthenticationManager AuthenticationManager
{
get
{
return _authenticationManager ?? HttpContext.GetOwinContext().Authentication;
}
set
{
_authenticationManager = value;
}
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Index(LoginViewModel accountUser)
{
if (!ModelState.IsValid)
return View(accountUser);
var systemUser = _authenticationService.Verify(accountUser.Email, accountUser.Password);
if (systemUser == null)
{
ModelState.AddModelError("", "Invalid email address or password");
return View(accountUser);
}
var identity = new ClaimsIdentity
(
new[]
{
new Claim(ClaimTypes.Name, systemUser.FullName),
new Claim(ClaimTypes.Email, systemUser.Email)
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name,
ClaimTypes.Role
);
// Set roles for authorization attributes used throughout dashboard
foreach(var role in systemUser.Roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role.Name));
}
AuthenticationManager.SignIn
(
new AuthenticationProperties
{
IsPersistent = true
},
identity
);
return Redirect(accountUser.ReturnUrl);
}
LoginControllerTests:
[TestClass]
public class LoginControllerTests
{
private readonly Mock<IAuthenticationManager> _mockAuthenticationManager;
private readonly Mock<IAuthenticationService> _mockAuthenticationService;
public LoginControllerTests()
{
_mockAuthenticationManager = new Mock<IAuthenticationManager>();
_mockAuthenticationService = new Mock<IAuthenticationService>();
}
private LoginController GetLoginControllerInstance()
{
var controller = new LoginController(_mockAuthenticationService.Object);
controller.AuthenticationManager = _mockAuthenticationManager.Object;
return controller;
}
[TestMethod]
public void Login_Verified_Authenticates_System_User()
{
// Arrange
var viewModel = new LoginViewModel("/")
{
Email = "email#test.co.uk",
Password = "password-test"
};
var systemUser = new SystemUser()
{
Id = new Random().Next(),
FirstName = "Joe",
LastName = "Bloggs",
Email = viewModel.Email,
Password = viewModel.Password,
Roles = new List<Role>()
};
var identity = new ClaimsIdentity
(
new[]
{
new Claim(ClaimTypes.Name, systemUser.FullName),
new Claim(ClaimTypes.Email, systemUser.Email)
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name,
ClaimTypes.Role
);
var authenticationProperty = new AuthenticationProperties
{
IsPersistent = true
};
var controller = this.GetLoginControllerInstance();
_mockAuthenticationService.Setup(m => m.Verify(viewModel.Email, viewModel.Password))
.Returns(systemUser);
_mockAuthenticationManager.Setup(m => m.SignIn(authenticationProperty, identity));
// Act
var result = controller.Index(viewModel);
// Assert
Assert.IsNotNull(result);
Assert.IsTrue(controller.ModelState.IsValid);
_mockAuthenticationService.Verify(m => m.Verify(viewModel.Email, viewModel.Password), Times.Once);
_mockAuthenticationManager.Verify(m => m.SignIn(authenticationProperty, identity), Times.Once);
}
}
}
Running in deubg the method gets called within the controller just fine but Moq appears to be ignoring it.
Any ideas?
Nkosi said it in a comment to your question:
In the expression
m => m.SignIn(authenticationProperty, identity)
the two arguments authenticationProperty and identity must be "equal" to the two arguments actually passed in (by the System-Under-Test). So does Equals return true when you compare?
You can use It.IsAny<AuthenticationProperties>() or It.Is((AuthenticationProperties x) => x.IsPersistent) or similar, if needed. And analogous for the ClaimsIdentity. Then Moq will no longer require Equals equality.
I have ChangePassword method where I have User.Identity.GetUserId() to find UserId.
Problem: It always return null. Don't understand why.
I read in another post that the GetUserById use below line of code to find Id. I am not sure how do I mock ClaimsTypes.NameIdentifier.
return ci.FindFirstValue(ClaimTypes.NameIdentifier);
ChangePassword method (method to be unit testes)
public async Task<IHttpActionResult> ChangePassword(string NewPassword, string OldPassword)
{
_tstService = new TestService();
IdentityResult result = await _tstService.ChangePassword(User.Identity.GetUserId(), OldPassword, NewPassword);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
Unit Test
var mock = new Mock<MyController>();
mock.CallBase = true;
var obj = mock.Object;
obj.ControllerContext = new HttpControllerContext { Request = new HttpRequestMessage() };
obj.Request.SetOwinContext(CommonCodeHelper.mockOwinContext());
IPrincipal user = GetPrincipal();
obj.ControllerContext.RequestContext.Principal = user;
var result = await obj.ChangePassword(dto);
//GetPrincipal
public static IPrincipal GetPrincipal()
{
var user = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
identity.Setup(x => x.Name).Returns("User1#Test.com");
identity.Setup(p => p.IsAuthenticated).Returns(true);
user.Setup(x => x.Identity).Returns(identity.Object);
Thread.CurrentPrincipal = user.Object;
return user.Object;
}
IOwinContext mocking code
public static IOwinContext mockOwinContext()
{
var owinMock = new Mock<IOwinContext>();
owinMock.Setup(o => o.Authentication.User).Returns(new ClaimsPrincipal());
owinMock.Setup(o => o.Request).Returns(new Mock<OwinRequest>().Object);
owinMock.Setup(o => o.Response).Returns(new Mock<OwinResponse>().Object);
owinMock.Setup(o => o.Environment).Returns(new Dictionary<string, object> { { "key1", 123 } });
var traceMock = new Mock<TextWriter>();
owinMock.Setup(o => o.TraceOutput).Returns(traceMock.Object);
var userStoreMock = new Mock<IUserStore<IfsUser>>();
userStoreMock.Setup(s => s.FindByIdAsync("User1#ifstoolsuite.com")).ReturnsAsync(new IfsUser
{
Id = "User1#test.com",
FirstName = "Test",
LastName = "User1",
Email = "User1#test.com",
UserName = "User1#test.com",
});
var applicationUserManager = new IfsUserManager(userStoreMock.Object);
owinMock.Setup(o => o.Get<IfsUserManager>(It.IsAny<string>())).Returns(applicationUserManager);
return owinMock.Object;
}
Your GetPrincipal can be updated to use claims.
public static IPrincipal GetPrincipal() {
//use an actual identity fake
var username = "User1#Test.com";
var identity = new GenericIdentity(username, "");
//create claim and add it to indentity
var nameIdentifierClaim = new Claim(ClaimTypes.NameIdentifier, username);
identity.AddClaim(nameIdentifierClaim);
var user = new Mock<IPrincipal>();
user.Setup(x => x.Identity).Returns(identity);
Thread.CurrentPrincipal = user.Object;
return user.Object;
}
Here is an example that shows how the above approach works.
public partial class MiscUnitTests {
[TestClass]
public class IdentityTests : MiscUnitTests {
Mock<IPrincipal> mockPrincipal;
string username = "test#test.com";
[TestInitialize]
public override void Init() {
//Arrange
var identity = new GenericIdentity(username, "");
var nameIdentifierClaim = new Claim(ClaimTypes.NameIdentifier, username);
identity.AddClaim(nameIdentifierClaim);
mockPrincipal = new Mock<IPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
}
[TestMethod]
public void Should_GetUserId_From_Identity() {
var principal = mockPrincipal.Object;
//Act
var result = principal.Identity.GetUserId();
//Asserts
Assert.AreEqual(username, result);
}
[TestMethod]
public void Identity_Should_Be_Authenticated() {
var principal = mockPrincipal.Object;
//Asserts
Assert.IsTrue(principal.Identity.IsAuthenticated);
}
}
}
You have some design issues. Creating a concrete TestService will cause problems if it as connecting to an actual implementation. That becomes an integration test. Abstract that dependency as well.
public interface ITestService {
Task<IdentityResult> ChangePassword(string userId, string oldPassword, string newPassword);
}
public abstract class MyController : ApiController {
private ITestService service;
protected MyController(ITestService service) {
this.service = service;
}
public async Task<IHttpActionResult> ChangePassword(string NewPassword, string OldPassword) {
IdentityResult result = await service.ChangePassword(User.Identity.GetUserId(), OldPassword, NewPassword);
if (!result.Succeeded) {
return GetErrorResult(result);
}
return Ok();
}
}
Also you should not mock the System under test. You should mock the dependencies of the SUT. Based on your method to be tested and what you indicated in the comments that MyController is an abstract class, the following test should apply
[TestClass]
public class MyControllerTests {
public class FakeController : MyController {
public FakeController(ITestService service) : base(service) { }
}
[TestMethod]
public void TestMyController() {
//Arrange
var mockService = new Mock<ITestService>();
mockService
.Setup(m => m.ChangePassword(....))
.ReturnsAsync(....);
var controller = new FakeController(mockService.Object);
//Set a fake request. If your controller creates responses you will need this
controller.Request = new HttpRequestMessage {
RequestUri = new Uri("http://localhost/api/my")
};
controller.Configuration = new HttpConfiguration();
controller.User = GetPrincipal();
//Act
var result = await controller.ChangePassword("NewPassword", "OldPassword");
//Assert
//...
}
}