0

Using .NET Core 2.2, I'm trying to test a Razor Page using Identity Framework that starts like this

var user = await _userManager.GetUserAsync(User);

There are lots of solutions online that can be summed up by this generic class: setting up the UserManager on a IUserStore mock, and setting up the User on the PageContext.

public class SetupContext<T> where T : IdentityUser<Guid>, new()
{
    public Mock<IUserRoleStore<T>> MockUserStore { get; private set; }

    public UserManager<T> SetupUserManager()
    {
        MockUserStore = new Mock<IUserStore<T>>().As<IUserRoleStore<T>>();
        MockUserStore.Setup(x => x.FindByIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(
            (string id, CancellationToken token) => new T()
            {
                Id = Guid.Parse(id),
                UserName = "User name"
            });
        return new UserManager<T>(MockUserStore.Object, null, null, null, null, null, null, null, null);
    }

    public PageContext SetupPageContext()
    {
        var displayName = "User name";
        var identity = new GenericIdentity(displayName);
        var principle = new ClaimsPrincipal(identity);
        var httpContext = new DefaultHttpContext()
        {
            User = principle
        };
        var modelState = new ModelStateDictionary();
        var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
        var modelMetadataProvider = new EmptyModelMetadataProvider();
        var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
        var pageContext = new PageContext(actionContext)
        {
            ViewData = viewData
        };
        return pageContext;
    }
}

The problem is that this won't work. GetUserAsync calls GetUserId which calls

principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType)

This claim isn't setup, in fact no ID is setup anywhere, thus it can't return the user nor the user id. Then later I'll want to access database data related to UserId for testing.

I've spent quite a bit of time searching and haven't found the solution. It looks like I'm 90% there but what more do I need to set up the test correctly?

The test I'll run will be setup like this

private async Task<FreeTrialModel> SetupModelAsync()
{
    _db = new RemoteHealingTechDbContext(InMemoryDbContext<RemoteHealingTechDbContext>.GetTestDbOptions());
    await SeedData.Initialize(_db);
    var _setup = new SetupContext<ApplicationUser>();
    var userManager = _setup.SetupUserManager();
    var pageContext = _setup.SetupPageContext();
    return new FreeTrialModel(_db, userManager)
    {
        PageContext = pageContext
    };
}
Etienne Charland
  • 3,424
  • 5
  • 28
  • 58

1 Answers1

1

After more digging I finally found the answer here

I must use ClaimsIdentity containing a Claims instead of a GenericIdentity. Replace

var identity = new GenericIdentity(displayName);

with

var identity = new ClaimsIdentity(new Claim[]
{
    new Claim(ClaimTypes.NameIdentifier, userName, null)
});

Then, the FindByIdAsync method takes the UserName and not the UserId so I changed the declaration to

MockUserStore.Setup(x => x.FindByIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(
    (string userName, CancellationToken token) => new T()
    {
        Id = userId,
        UserName = userName
    });

Overall solution

/// <summary>
/// Configures the .Net Core Identity Framework for unit testing.
/// </summary>
/// <typeparam name="T">The type of IdentityUser used for the framework.</typeparam>
/// <typeparam name="TKey">The data type of primary keys.</typeparam>
public class SetupContext<T, TKey> where T : IdentityUser<TKey>, new() where TKey : IEquatable<TKey>
{
    /// <summary>
    /// Returns the generated IUserRoleStore mock.
    /// </summary>
    public Mock<IUserRoleStore<T>> MockUserStore { get; private set; }

    /// <summary>
    /// Returns a UserManager based on a IUserRoleStore mock.
    /// </summary>
    /// <param name="userId">The UserId to set on generated IdentityUser objects.</param>
    /// <returns>A new UserManager for unit testing.</returns>
    public UserManager<T> SetupUserManager(TKey userId = default(TKey))
    {
        MockUserStore = new Mock<IUserStore<T>>().As<IUserRoleStore<T>>();
        MockUserStore.Setup(x => x.FindByIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(
            (string userName, CancellationToken token) => new T()
            {
                Id = userId,
                UserName = userName
            });
        return new UserManager<T>(MockUserStore.Object, null, null, null, null, null, null, null, null);
    }

    /// <summary>
    /// Returns a PageContext to set on a page, containing a logged in user.
    /// </summary>
    /// <param name="userName">The user name to set on the ClaimsIdentity.</param>
    /// <returns>A new PageContext object.</returns>
    public PageContext SetupPageContext(string userName = "User name")
    {
        var identity = new ClaimsIdentity(new Claim[]
        {
            new Claim(ClaimTypes.NameIdentifier, userName, null)
        });
        var principle = new ClaimsPrincipal(identity);
        var httpContext = new DefaultHttpContext()
        {
            User = principle
        };
        var modelState = new ModelStateDictionary();
        var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
        var modelMetadataProvider = new EmptyModelMetadataProvider();
        var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
        var pageContext = new PageContext(actionContext)
        {
            ViewData = viewData
        };
        return pageContext;
    }
}
Etienne Charland
  • 3,424
  • 5
  • 28
  • 58