1

I am trying to unit test a Reports controller that takes a UserManager object in the constructor.

public class ReportController : BaseReportController { private readonly IUserService _userService; public ReportController ( IOptions<AppSettings> appSettings, UserManager<ApplicationUser> userManager, IUserService userService ) : base( appSettings, userManager ) { _userService = userService; } public async Task<ActionResult> Report ( string path ) { var currentUser = await GetCurrentUserAsync(); var excludedItems = _userService.GetUserExcludedReportsById( currentUser.Id ).Select( er => er.Path ); if ( string.IsNullOrEmpty( path ) || excludedItems.Any( path.Contains ) ) { return RedirectToAction( nameof(HomeController.Index), "Home" ); } var customItems = _userService.GetUserCustomReportsById( currentUser.Id ).Select( er => er.Path ); if ( path.Contains( AppSettings.CustomReportsFolderName ) && !customItems.Any( path.Contains ) ) { return RedirectToAction( nameof(HomeController.Index), "Home" ); } var model = GetReportViewerModel( Request ); model.Parameters.Clear(); var dbname = _userService.GetDefaultDbName( (await GetCurrentUserAsync()).Id ); model.Parameters.Add( "connectionStr", new[] { dbname } ); //model.ReportPath = "/Portal Reports" + path; model.ReportPath = path; model.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Ntlm; return View( "Report", model ); } } 

I think I need to somehow provide an application user to call this. So GetCurrentUserAsync() would be the first example. This passes to the base:

public virtual Task<ApplicationUser> GetCurrentUserAsync() { return UserManager.GetUserAsync(HttpContext.User); } 

I have created a FakeUserManager, but it still errors on the identity parts with a null exception. This is my test so far:

[Fact] public void ReportControllerReturnsToIndexIfPathIsNull() { //Arrange var context = new Mock<HttpContext>(); context.Setup(x => x.User).Returns(user.Object); var mockUserService = new Mock<IUserService>(); AppSettings appSettings = new AppSettings() { }; IOptions<AppSettings> options = Options.Create(appSettings); var mockUserStore = new Mock<IUserStore<ApplicationUser>>(); var sut = new ReportController(options, new FakeUserManager(mockUserStore.Object), mockUserService.Object); sut.HttpContext = context.Object; //Act var result = sut.Report(""); //Assert } } public class FakeUserManager : UserManager<ApplicationUser> { public FakeUserManager(IUserStore<ApplicationUser> userStore) : base(userStore/*new Mock<IUserStore<ApplicationUser>>().Object*/, new Mock<IOptions<IdentityOptions>>().Object, new Mock<IPasswordHasher<ApplicationUser>>().Object, new IUserValidator<ApplicationUser>[0], new IPasswordValidator<ApplicationUser>[0], new Mock<ILookupNormalizer>().Object, new Mock<IdentityErrorDescriber>().Object, new Mock<IServiceProvider>().Object, new Mock<ILogger<UserManager<ApplicationUser>>>().Object) { } public override Task<ApplicationUser> FindByIdAsync(string id) { return Task.FromResult(new ApplicationUser { Id = id }); } } 

Am i on the right line here? How can I pass this a httpcontext.user? Many thanks.

1 Answer 1

3

You don't technically need to mock GetUserAsync, and it's actually better if you don't. All you need is to mock HttpContext such that the right user is returned.

That's a significantly easier hurdle to jump and actually provides a better test. If you mock GetUserAsync, you're testing a particular implementation of an action, i.e. one that uses GetUserAsync to fetch the user. If you later decided to get the user through some service class, directly from your context, etc. then your test would break.

Meanwhile, as long as you query the current user based on HttpContext, which any method would obviously use, because that's where the concept of the "current" user lives, then you're not tied to any one implementation. As long as the action somehow gets the current user, it works.

UPDATE

var controller = new ReportController(); controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "foo") })); } }; 

You may need to play around a bit with the claims set on the user, depending on what your code is doing. The above serves to set the value of User.Identity.Name and should be enough for most purposes. The full list of standard claims is here.

Sign up to request clarification or add additional context in comments.

2 Comments

Great. So I'm very new to this - How can I set the context for the controller? Do i need to define a test ApplicationUser somehow as well? Using the example in my question, i changed the test in the above but i cannot set the httpcontext for the controller as it is read only.
Once I call the GetUserAcyns from my controller it returns null. How do you make it works ?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.