Unit testing methods that depend upon ASP.NET Identity 2 UserManager
ASP.NET Identity has made it possible to unit test code that makes use of its built in user and role management features, generally by injecting a UserManager instance into your classes. It can be frustrating however, to find that the approach we usually follow for mocking dependencies is not feasible. If you resort to Google and Stackoverflow you will be told to just mock the interfaces that your code under test requires (via UserManager). This is true, but let’s dig into it a bit more so we can understand what’s really going on and apply it to multiple scenarios.
Our example — SweetShop
Our contrived example is a sweet shop. The SweetShop project is a class library that contains a single service class (SweetService) and a basic implementation of ASP.NET Identity’s IUser interface (ApplicationUser). The only NuGet package added to the project was Microsoft.AspNet.Identity.Core (v2.2.1).
Here is our complete SweetService class:
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
namespace SweetShop
{
public class SweetService
{
private readonly UserManager<ApplicationUser> _userManager;
public SweetService(
UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<int> NumberOfGummyBearsForUser(string userId)
{
var userHasASweetTooth = await _userManager.IsInRoleAsync(userId, "SweetTooth");
if (userHasASweetTooth)
{
return 100;
}
else
{
return 1;
}
}
}
}
Our SweetService takes an instance of UserManager<ApplicationUser> as a dependency; this is injected through the constructor. The class has a single method, NumberOfGummyBearsForUser, which given a userId will see if that user is in the “SweetTooth” role and return 100 if true and 1 otherwise.
We want to test that our NumberOfGummyBearsForUser method returns the correct number of gummy bears to each user, because to only give 1 gummy bear to someone with a sweet tooth would be a great insult. So we look at the code and deduce that we need to mock the UserManager dependency and setup the IsInRoleAsync method to return true or false and test that we get the expected result from our method.
Delving into ASP.NET Identity 2
Not so fast! Let’s go and take a look at the UserManager code to see how we should go about testing our code that depends on it. The ASP.NET Identity v2 code can viewed or downloaded from https://aspnetidentity.codeplex.com.
Open up the UserManager class (srcMicrosoft.AspNet.Identity.CoreUserManager.cs). The UserManager class we have as a dependency looks like this:
public class UserManager<TUser> : UserManager<TUser, string> where TUser : class, IUser<string>
{
public UserManager(IUserStore<TUser> store)
: base(store)
{
}
}
This is simply a convenience subclass of UserManager<TUser, TKey> that we can use when our user ID’s are going to be strings. The base class definition is as follows:
public class UserManager<TUser, TKey> : IDisposable
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
What we can see from these class definitions is that UserManager does not implement an interface that we can mock in our tests to provide the functionality our code under test requires. Nor does it have a default, parameterless constructor, so mocking the class directly is not going to be worth our while. UserManager does, however, accept an implementation of an IUserStore<TUser> and this is what we will be mocking in our test class.
Before we jump in and start writing our test, let’s go take a look at the UserManager.IsInRoleAsync method to see what the code our SweetService calls is actually doing.
public virtual async Task<bool> IsInRoleAsync(TKey userId, string role)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
var user = await FindByIdAsync(userId).WithCurrentCulture();
if (user == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound,
userId));
}
return await userRoleStore.IsInRoleAsync(user, role).WithCurrentCulture();
}
Hmm, what is GetUserRoleStore()? And I can make a guess at what FindByIdAsync does but let’s check its code to make sure.
private IUserRoleStore<TUser, TKey> GetUserRoleStore()
{
var cast = Store as IUserRoleStore<TUser, TKey>;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserRoleStore);
}
return cast;
}
public virtual Task<TUser> FindByIdAsync(TKey userId)
{
ThrowIfDisposed();
return Store.FindByIdAsync(userId);
}
OK, so FindByIdAsync is simply calling the corresponding method on the IUserStore implementation that was injected in the constructor. But GetUserRoleStore is casting that same IUserStore implementation to an IUserRoleStore. And it is an instance of this IUserRoleStore implementation that our UserManager.IsInRoleAsync uses to check if our user is in a given role. So, in summary, UserManager.IsInRoleAsync:
- checks to see if the store that has been provided supports user roles (i.e. that it implements IUserRoleStore)
- gets the IUser implementation for the given userId
- uses the implementation of IUserRoleStore.IsInRoleAsync to see if the user is in a specified role.
Writing our test
That’s enough information for us to go about setting up and executing our test. For my example I am using XUnit and Moq (installed via their NuGet packages) and I also installed the XUnit console and Visual Studio runner packages.
Our SweetService object under test requires a UserManager to be provided to its constructor. The UserManager in turn requires an implementation of IUserStore in its constructor. We can mock the IUserStore to achieve this, and we’ll end up with code like the following:
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object);
var objectUnderTest = new SweetService(userManager);
We’ll need to setup a couple of calls on our mock. First, we need to ensure an implementation of IUser is returned from the FindByIdAsync call (otherwise our test will throw an exception). This is simply a matter of returning an ApplicationUser (the very basic implementation of IUser we created in our SweetShop project).
mockUserStore.Setup(x => x.FindByIdAsync(userId))
.ReturnsAsync(new ApplicationUser()
{
UserName = "test@email.com"
});
Now we need to setup a call to IsInRoleAsync. But hang on a sec, we’ve mocked IUserStore and IsInRoleAsync is on IUserRoleStore. What’s worse, our UserManager constructor only accepts a single argument. You might try to simply create a mock of IUserRoleStore (since it inherits from IUserStore) and provide that to the UserManager or perhaps cast the IUserStore mock to an IUserRoleStore before setting up the IsInRoleAsync call but pain and suffering lies ahead for you if you try. Fortunately, Moq provides a solution. The Mock.As<T> method allows you to mock classes that implement multiple interfaces and it is perfect for our situation. The lines of code are simply as follows:
var mockUserRoleStore = mockUserStore.As<IUserRoleStore<ApplicationUser>>();
mockUserRoleStore.Setup(x => x.IsInRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(roleName.Equals(SWEET_TOOTH_ROLENAME, StringComparison.OrdinalIgnoreCase));
Which leaves our full test looking as follows:
[Theory]
[InlineData("SweetTooth", 100)]
[InlineData("CookieMonster", 1)]
public async void CorrectNumberOfGummyBearsForUsersRoleIsReturned(string roleName, int expectedNumberOfGummyBearsReturned)
{
// Arrange
const string SWEET_TOOTH_ROLENAME = "SweetTooth";
var userId = Guid.NewGuid().ToString();
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
var mockUserRoleStore = mockUserStore.As<IUserRoleStore<ApplicationUser>>();
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object);
mockUserStore.Setup(x => x.FindByIdAsync(userId))
.ReturnsAsync(new ApplicationUser()
{
UserName = "test@email.com"
});
mockUserRoleStore.Setup(x => x.IsInRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(roleName.Equals(SWEET_TOOTH_ROLENAME, StringComparison.OrdinalIgnoreCase));
var objectUnderTest = new SweetService(userManager);
// Act
var actualResult = await objectUnderTest.NumberOfGummyBearsForUser(userId);
// Assert
Assert.Equal(expectedNumberOfGummyBearsReturned, actualResult);
}
Wrapping up
We’ve covered quite a bit of ground here. When testing code that depends on ASP.NET Identity it is helpful (if not imperative) to understand what the underlying framework code is doing. This allows you to mock the appropriate interfaces that are being used. Moq’s As method allows you mock classes that implement multiple interfaces.
The source code for my example can be found here: https://github.com/alastairchristian/TestingSamples