Unit Testing ASP.NET MVC5 App - c#

I'm extending the ApplicationUser class by adding a new property (as shown in the tutorial
Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on (C#))
public class ApplicationUser : IdentityUser
{
public DateTime BirthDate { get; set; }
}
Now I want to create a Unit Test to verify that my AccountController is correctly saving the BirthDate.
I've created an in-memory user store named TestUserStore
[TestMethod]
public void Register()
{
// Arrange
var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
var controller = new AccountController(userManager);
// This will setup a fake HttpContext using Moq
controller.SetFakeControllerContext();
// Act
var result =
controller.Register(new RegisterViewModel
{
BirthDate = TestBirthDate,
UserName = TestUser,
Password = TestUserPassword,
ConfirmPassword = TestUserPassword
}).Result;
// Assert
Assert.IsNotNull(result);
var addedUser = userManager.FindByName(TestUser);
Assert.IsNotNull(addedUser);
Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}
The controller.Register method is boilerplate code generated by MVC5 but for reference purposes I'm including it here.
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName, BirthDate = model.BirthDate };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
When I call Register, it calls SignInAsync which is where the trouble will occur.
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
At the lowest layer, the boilerplate code includes this tidbit
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
This is where the root of the problm occurs. This call to GetOwinContext is an extension method which I cannot mock and I cannot replace with a stub (unless of course I change the boilerplate code).
When I run this test I get an exception
Test method MVCLabMigration.Tests.Controllers.AccountControllerTest.Register threw exception:
System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.HttpContextBaseExtensions.GetOwinEnvironment(HttpContextBase context)
at System.Web.HttpContextBaseExtensions.GetOwinContext(HttpContextBase context)
at MVCLabMigration.Controllers.AccountController.get_AuthenticationManager() in AccountController.cs: line 330
at MVCLabMigration.Controllers.AccountController.<SignInAsync>d__40.MoveNext() in AccountController.cs: line 336
In prior releases the ASP.NET MVC team worked very hard to make the code testable. It seems on the surface that now testing an AccountController is not going to be easy. I have some choices.
I can
Modify the boiler plate code so that it doesn't call an extension method and deal with this problem at that level
Setup the OWin pipeline for testing purposes
Avoid writing testing code that requires the AuthN / AuthZ infrastructure (not a reasonable option)
I'm not sure which road is better. Either one could solve this. My question boils down to which is the best strategy.
Note: Yes, I know that I don't need to test code that I didn't write. The UserManager infrastructure provided MVC5 is such a piece of infrastructure BUT if I want to write tests that verify my modifications to ApplicationUser or code that verifies behavior that depends upon user roles then I must test using UserManager.

I'm answering my own question so I can get a sense from the community if you think this is a good answer.
Step 1: Modify the generated AccountController to provide a property setter for the AuthenticationManager using a backing field.
// Add this private variable
private IAuthenticationManager _authnManager;
// Modified this from private to public and add the setter
public IAuthenticationManager AuthenticationManager
{
get
{
if (_authnManager == null)
_authnManager = HttpContext.GetOwinContext().Authentication;
return _authnManager;
}
set { _authnManager = value; }
}
Step 2:
Modify the unit test to add a mock for the Microsoft.OWin.IAuthenticationManager interface
[TestMethod]
public void Register()
{
// Arrange
var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
var controller = new AccountController(userManager);
controller.SetFakeControllerContext();
// Modify the test to setup a mock IAuthenticationManager
var mockAuthenticationManager = new Mock<IAuthenticationManager>();
mockAuthenticationManager.Setup(am => am.SignOut());
mockAuthenticationManager.Setup(am => am.SignIn());
// Add it to the controller - this is why you have to make a public setter
controller.AuthenticationManager = mockAuthenticationManager.Object;
// Act
var result =
controller.Register(new RegisterViewModel
{
BirthDate = TestBirthDate,
UserName = TestUser,
Password = TestUserPassword,
ConfirmPassword = TestUserPassword
}).Result;
// Assert
Assert.IsNotNull(result);
var addedUser = userManager.FindByName(TestUser);
Assert.IsNotNull(addedUser);
Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}
Now the test passes.
Good idea? Bad idea?

My needs are similar, but I realized that I don't want a pure unit test of my AccountController. Rather I want to test it in an environment that is as close as possible to its natural habitat (integration test, if you want). So I don't want to mock the surrounding objects, but use the real ones, with as little of my own code as I can get away with.
The HttpContextBaseExtensions.GetOwinContext method also got in my way, so I was very happy with Blisco's hint.
Now the most important part of my solution looks like this:
/// <summary> Set up an account controller with just enough context to work through the tests. </summary>
/// <param name="userManager"> The user manager to be used </param>
/// <returns>A new account controller</returns>
private static AccountController SetupAccountController(ApplicationUserManager userManager)
{
AccountController controller = new AccountController(userManager);
Uri url = new Uri("https://localhost/Account/ForgotPassword"); // the real string appears to be irrelevant
RouteData routeData = new RouteData();
HttpRequest httpRequest = new HttpRequest("", url.AbsoluteUri, "");
HttpResponse httpResponse = new HttpResponse(null);
HttpContext httpContext = new HttpContext(httpRequest, httpResponse);
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
httpContext.Items.Add("owin.Environment", owinEnvironment);
HttpContextWrapper contextWrapper = new HttpContextWrapper(httpContext);
ControllerContext controllerContext = new ControllerContext(contextWrapper, routeData, controller);
controller.ControllerContext = controllerContext;
controller.Url = new UrlHelper(new RequestContext(contextWrapper, routeData));
// We have not found out how to set up this UrlHelper so that we get a real callbackUrl in AccountController.ForgotPassword.
return controller;
}
I have not yet succeeded to get everything working (in particular, I could not get UrlHelper to produce a proper URL in the ForgotPassword method), but most of my needs are covered now.

I've used a solution similar to yours - mocking an IAuthenticationManager - but my login code is in a LoginManager class that takes the IAuthenticationManager via constructor injection.
public LoginHandler(HttpContextBase httpContext, IAuthenticationManager authManager)
{
_httpContext = httpContext;
_authManager = authManager;
}
I'm using Unity to register my dependencies:
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<HttpContextBase>(
new InjectionFactory(_ => new HttpContextWrapper(HttpContext.Current)));
container.RegisterType<IOwinContext>(new InjectionFactory(c => c.Resolve<HttpContextBase>().GetOwinContext()));
container.RegisterType<IAuthenticationManager>(
new InjectionFactory(c => c.Resolve<IOwinContext>().Authentication));
container.RegisterType<ILoginHandler, LoginHandler>();
// Further registrations here...
}
However, I'd like to test my Unity registrations, and this has proved tricky without faking (a) HttpContext.Current (hard enough) and (b) GetOwinContext() - which, as you've found, is impossible to do directly.
I've found a solution in the form of Phil Haack's HttpSimulator and some manipulation of the HttpContext to create a basic Owin environment. So far I've found that setting a single dummy Owin variable is enough to make GetOwinContext() work, but YMMV.
public static class HttpSimulatorExtensions
{
public static void SimulateRequestAndOwinContext(this HttpSimulator simulator)
{
simulator.SimulateRequest();
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
HttpContext.Current.Items.Add("owin.Environment", owinEnvironment);
}
}
[TestClass]
public class UnityConfigTests
{
[TestMethod]
public void RegisterTypes_RegistersAllDependenciesOfHomeController()
{
IUnityContainer container = UnityConfig.GetConfiguredContainer();
HomeController controller;
using (HttpSimulator simulator = new HttpSimulator())
{
simulator.SimulateRequestAndOwinContext();
controller = container.Resolve<HomeController>();
}
Assert.IsNotNull(controller);
}
}
HttpSimulator may be overkill if your SetFakeControllerContext() method does the job, but it looks like a useful tool for integration testing.

Related

How do I unit test a controller that is decorated with the ServiceFilterAttribute, custom ActionFilter implementation

Summary:
I'm trying to test a controller with an ActionFilter implementation
Unit test fails, because ActionFilter does not get invoked in unit test.
Testing via Postman works as expected and the correct result is achieved.
Can the controller be tested like this or should it move to integration test?
Breakdown:
I'm able to test the ActionFilter on its own in a unit test, what I would like to do is test the controller in a unit test.
The action filter looks like this:
public class ValidateEntityExistAttribute<T> : IActionFilter
where T : class, IEntityBase
{
readonly AppDbContext _appDbContext;
public ValidateEntityExistAttribute(AppDbContext appDbContext)
{
this._appDbContext = appDbContext;
}
public void OnActionExecuted(ActionExecutedContext context)
{}
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ActionArguments.ContainsKey("id"))
{
context.Result = new BadRequestObjectResult("The id must be passed as parameter");
return;
}
int id = (int)context.ActionArguments["id"];
var foundEntity = _appDbContext.Set<T>().Find(id);
if (foundEntity == null)
context.Result = new NotFoundResult();
else
context.HttpContext.Items.Add("entity_found", foundEntity);
}
}
The ActionFilter implementation is added to the services in the startup file
ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<ValidateEntityExistAttribute<Meeting>>();
...
}
The filter can be applied to any controller method that needs to check if an entity exists. ie the GetById method.
[HttpGet("{id}")]
[ServiceFilter(typeof(ValidateEntityExistAttribute<Meeting>))]
public async Task<ActionResult<MeetingDto>> GetById(int id)
{
var entity = HttpContext.Items["entity_found"] as Meeting;
await Task.CompletedTask;
return Ok(entity.ConvertTo<MeetingDto>());
}
In the xUnit test I have set up the test to test the controller like this:
[Fact]
public async Task Get_Meeting_Record_By_Id()
{
// Arrange
var _AppDbContext = AppDbContextMocker.GetAppDbContext(nameof(Get_All_Meeting_Records));
var _controller = InitializeController(_AppDbContext);
//Act
var all = await _controller.GetById(1);
//Assert
Assert.Equal(1, all.Value.Id);
//clean up otherwise the other test will complain about key tracking.
await _AppDbContext.DisposeAsync();
}
and this is what the InitializeController method look like, I left the commented lines so that it is visible to what I have tried, none of the commented code worked.
I mocked and used the default classes.
private MeetingController InitializeController(AppDbContext appDbContext)
{
var _controller = new MeetingController(appDbContext);
var spf = new DefaultServiceProviderFactory(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true });
var sc = spf.CreateBuilder(new ServiceCollection());
sc.AddMvc();
sc.AddControllers();
//(config =>
//{
// config.Filters.Add(new ValidateModelStateAttribute());
// config.Filters.Add(new ValidateEntityExistAttribute<Meeting>(appDbContext));
//});
sc.AddTransient<ValidateModelStateAttribute>();
sc.AddTransient<ValidateEntityExistAttribute<Meeting>>();
var sp = sc.BuildServiceProvider();
//var mockHttpContext = new Mock<HttpContext>();
var httpContext = new DefaultHttpContext
{
RequestServices = sp
};
//mockHttpContext.Setup(cx => cx.RequestServices).Returns(sp);
//var contDesc = new ControllerActionDescriptor();
//var context = new ControllerContext();
//var context = new ControllerContext(new ActionContext(mockHttpContext.Object, new RouteData(), contDesc));
//context.HttpContext = mockHttpContext.Object;
//context.HttpContext = httpContext;
//_controller.ControllerContext = context;
_controller.ControllerContext.HttpContext = httpContext;
return _controller;
}
The issues I have is that when running the unit test the ActionFilter implementation is never invoked, thus breaking the test because var entity = HttpContext.Items["entity_found"] as Meeting; in the controller is always null! more accurately HttpContext.Items is always null.
The break point in the ActionFilter never gets hit.
When this is tested via postman it all works as expected, and the break point gets hit
Is there a way to test the controller as a unit test this way, or should this test now just move to integration?
Thank you #Fei Han for the link about unit testing controllers.
As it turns out this is by design, as stated in the microsoft documentation
Unit testing controllers
Set up unit tests of controller actions to
focus on the controller's behavior. A controller unit test avoids
scenarios such as filters, routing, and model binding. Tests that
cover the interactions among components that collectively respond to a
request are handled by integration tests.
So the test should move to integration testing.
In this particular scenario, where there is no detailed implementation for the GetById(int id) method, there is almost no value in doing a unit test for it.
If the GetById(int id) method had a more complex implementation, or if the ActionFilter did not prevent further processing, the HttpContext.Items["entity_found"] should be mocked.

Integration tests with asp.net core (test of controllers without the views)

I am trying to setup a test project to test my controllers with identity and the database without having to define the views.
I have a unit test project where I can test my controller by instanciating it, passing the dbContext to the constructor.
public class EventControllerTests
{
private readonly IEventRepository _eventRepository;
private readonly EventController _controller;
private readonly AppDbContext dbContext;
const string cn = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=EventDb;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
public EventControllerTests()
{
var options = new DbContextOptionsBuilder<EVNTS.Web.Database.AppDbContext>()
.UseSqlServer(cn).Options;
dbContext = new EVNTS.Web.Database.AppDbContext(options);
// Arrange
_eventRepository = new EventRepository(dbContext);
_controller = new EVNTS.Web.Controllers.EventController(_eventRepository);
}
[Fact]
public void ActionIndexTest()
{
// Act
var result = _controller.Index(1);
// Assert
var model = (Event)result.Model;
Assert.Equal(1, model.Id);
}
}
I have an integration test project where I use a WebApplicationFactory
public class BasicTests : IClassFixture<WebApplicationFactory<EVNTS.Startup>>
{
private readonly WebApplicationFactory<EVNTS.Startup> _factory;
private readonly HttpClient _client;
public BasicTests(WebApplicationFactory<EVNTS.Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
[Theory]
[InlineData("/")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Act
var response = await _client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
[Fact]
public async Task TestUserRegistration()
{
var s = _factory.Services.GetRequiredService<EVNTS.Web.Repositories.IEventRepository>();
var url = "/user/register";
var inputModel = new EVNTS.Web.ViewModels.RegisterModel()
{
UserName = "eric",
Password = "123456",
ConfirmPassword = "123456"
};
var sObj = JsonSerializer.Serialize(inputModel);
var content = new StringContent(sObj, Encoding.UTF8, "application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _client.PostAsync(url, content);
var result = response.Content.ReadAsStringAsync();
}
}
The problem is that with the second option, the views have to be created and I need to use a library like AngleSharp to test the results.
I would like something in between where I can call the contructor directly and test the result view but with the DI injecting the UserManager and the dbContext for me.
any ideas?
Cheers
Here is the controller:
public class UserController : Controller
{
private readonly UserManager<User> _userManager;
public UserController(UserManager<User> userManager)
{
_userManager = userManager;
}
[HttpPost]
public async Task<IActionResult> Register([FromBody] RegisterModel model)
{
IdentityResult? result=null;
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.UserName);
if (user == null)
{
user = new User
{
Id = Guid.NewGuid(),
UserName = model.UserName,
};
result = await _userManager.CreateAsync(user, model.Password);
}
}
return View(result);
}
}
I also find this usefull sometimes when you want to check the result of a controller in an integration test condition without checking the view.
You can use the dependency injection and create a scope from the WebApplicationFactory.
using (var serviceScope = Factory.Services.CreateScope())
{
var sut= serviceScope.ServiceProvider.GetService<YourController>();
}
To make this work you have to call the method AddControllersAsServices() in Startup.cs to register the controller in the DI container
services.AddControllersWithViews(options => { options.ConfigureMvcOptionsForPortalModule(); })
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddControllersAsServices();//add controller in DI to access it in integration testing
There is no in between. The first example is a unit test, while the second is an integration test. If you want to just look at the result object of the action, then you'd use the unit test methodology, and you'd need to mock out your dependencies. Otherwise, you'd use the integration test approach, and you have to deal with the actual simulated server response.
For what it's worth here, controller actions should be integration tested, since they are inherently dependent on a number of components coming together, so you should be following the second approach, parsing the HTML response, if necessary.
I don't consider myself an authority on how to perform unit testing, but since the comment section is restricted in characters I will write my comments here.
Usually, when you find yourself in a situation where it's difficult to come up with a good unit test (I won't define "good" here) more often than not, it is because there are some problems with the project structure/code design, and not actual limitations of the unit testing itself (again, not that unit testing doesn't have it's limitations, but I think this is not the case here).
Based on the above I asked you to include the action's code so we can examine what exactly are you trying to test and why it is so hard.
Here comes the heavily opinion-based part of my comment, but I leave it up to you wether you would want to take some of this or leave it.
It's not a rule, but a good rule of thumb is that the controller should contain very little business logic, which means that unit testing a controller should be basically testing the different paths that the request could go, once it hits the controller.
Generally you would want something like this:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = await _userManager.FindByNameAsync(model.UserName);
...
return View(result);
which then you can unit test with something like this:
public async Task Register_Returns_BadRequest_On_Invalid_Model()
{
var testUsername = "TestUsername";
var mockUserManager = new Mock<IUserManager>();
mockUserManager.Setup(m => m.FindByNameAsync(testUsername))
.Returns(Task.FromResult(**Not sure about this part**))
var controller = new RegisterController(mockUserManager.Object);
var result = await controller.Register(model: null);
var actionResult = Assert.IsType<ActionResult<IdentityResult>>(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}
For the happy path you want only to check that on a valid ModelState the result is of type ActionResult>
What is my idea:
When you unit test the controller you should not be bothered by the actual data, this is responsibility of other parts of the application
The controller unit test should be plain simple, most of the time you should be testing only those two cases - invalid data returns some sort of BadRequest, valid data returns the expected response
If you find yourself mocking too much objects most of the time it's a clear sign that you need some additional layer of abstraction.
In your case, in order to make my code better structured and easier for testing I would do the following:
First test for invalid ModelState - you don't want to proceed if the ModelState is invalid and this should also be covered by an unit test.
Managers should be a higher level of abstraction. Methods like FindByNameAsync and CreateAsync are more suitable for the data access layer. In the case of this action, your UserManager can have a method like Register so your controller's action look like this:
if (!ModelState.IsValid)
{
return BadRequest()
}
var result = _userManager.Register(model.UserName);
return View(result);
Now you can remove the Find and Create methods from the controller and create a UserRepository where I thin those methods belong and where you can test them in isolation.
In this setup You have these abstractions Controller -> Manager -> Repository. Now you try to test the three of these in one single method, which is causing the problems in my opinion.
Also, just because I find this a bit more tidy, usually you use a Service layer and if the structure is too complex you add the manager layer so it becomes Controller -> Manager -> Service -> Repository. In your case I'm not sure that you need this complexity so maybe just for the sake of better naming, rename the UserManager to UserService, so that your code flow is Controller -> Service -> Repository.
Also, last pease of advice. Controller testing has always been contraversial so don't be too bothered if you don't cover your controller with unit tests as much as other parts of the code. This is somewhat expected, what I wanted to tell with this post is mainly that the problem wasn't how to test but rather is the code testable as it is, which in my opinion could be improved as I've shown above. Yes, my proposition is not perfect as well but it creates smaller chunks of code which are encapsulated, don't have that many dependencies which ultimately makes them easier to test. And of course this is not a substitution of the integrations tests.
Hope this gave you some food for thought.

Unit Test - How to set controller user to Generic Principal object

Updated full solution:
WebApi Controller method which I'm going to test :
using Microsoft.AspNet.Identity;
using System.Web.Http;
[Authorize]
public class GigsController : ApiController
{
private readonly IUnitOfWork _unitOfWork;
public GigsController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
[HttpDelete]
public IHttpActionResult Cancel(int id)
{
var userId = User.Identity.GetUserId();
var gig = _unitOfWork.Gigs.GetGigWithAttendees(id);
if (gig.IsCanceled)
return NotFound();
if (gig.ArtistId != userId)
return Unauthorized();
gig.Cancel();
_unitOfWork.Complete();
return Ok();
}
}
Unit Test class :
[TestClass]
public class GigsControllerTests
{
private GigsController _controller;
public GigsControllerTests()
{
var identity = new GenericIdentity("user1#domain.com");
identity.AddClaim(
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "user1#domain.com"));
identity.AddClaim(
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "1"));
var principal = new GenericPrincipal(identity, null);
var mockUoW = new Mock<IUnitOfWork>();
_controller = new GigsController(mockUoW.Object);
_controller.User = principal;
}
I'm getting following error :
Error CS0200 Property or indexer 'ApiController.User' cannot be
assigned to -- it is read only
https://i.stack.imgur.com/YDQJS.png
You can assign it through ControllerContext
var user = new ClaimsPrincipal();
var context = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = user
}
};
controllerUnderTest.ControllerContext = context;
I ran into this same issue while following Moshi's tutorials, and this stumped me for a good couple of hours before I was finally able to resolve this.
Ultimately, what fixed this for me was changing my using statement from:
using GigHub.Controllers;
to
using GigHub.Controllers.Api;
Your GigsController _controller is pointing to the Controller itself and not the ApiController. I kinda wish Moshi had put "Api" in the name of his API classes that way you'd know if you were looking at the pure controller or the API version of the controller without having to check namespaces or go to definition on your objects.
You're right that the User property for the Controller class does indeed only have a Get.
public abstract class Controller : ControllerBase, IActionFilter,
IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter,
IResultFilter, IAsyncController, IController, IAsyncManagerContainer
{
public IPrincipal User { get; }
}
The User property for the ApiController class however has both a Get and Set.
public abstract class ApiController : IHttpController, IDisposable
{
public IPrincipal User { get; set; }
}
Hopefully this was your issue as well and helps you out!
Cheers!
Instead set the CurrentPrincipal of your executing thread
Thread.CurrentPrincipal = principal;
Came across this post when I'm trying to mock a user in a controller, rather than controller.apiā€¦ and struggled.
Anyway the way to do it is by overwriting ControllerContext in the controller with a mock object , and the mock object is set up to return the user you wish to mock.
var user = CreateLoggedInUser(targetUserID);
mockControllerContext = new Mock<ControllerContext>();
mockControllerContext.Setup(o => o.HttpContext.User).Returns(user);
myCaseController.ControllerContext = mockControllerContext.Object;
and
private ClaimsPrincipal CreateLoggedInUser(int userID)
{
GenericIdentity myIdentity = new GenericIdentity("apiTestUser");
myIdentity.AddClaims(new List<Claim> {
new Claim(ClaimTypes.Sid, userID.ToString()),
});
return new ClaimsPrincipal(myIdentity);
}

Unit Testing for Redirection Using Global Authorize Attribute [duplicate]

This is probably going to turn out to be a case of just needing another pair of eyes. I must be missing something, but I cannot figure out why this kind of thing cannot be tested for. I'm basically trying to ensure that unauthenticated users cannot access the view by marking the controller with the [Authorize] attribute and I'm trying to tests this using the following code:
[Fact]
public void ShouldRedirectToLoginForUnauthenticatedUsers()
{
var mockControllerContext = new Mock<ControllerContext>()
{ DefaultValue = DefaultValue.Mock };
var controller = new MyAdminController()
{ControllerContext = mockControllerContext.Object};
mockControllerContext.Setup(c =>
c.HttpContext.Request.IsAuthenticated).Returns(false);
var result = controller.Index();
Assert.IsAssignableFrom<RedirectResult>(result);
}
The RedirectResult I'm looking for is some kind of indication that the user is being redirected to the login form, but instead a ViewResult is always returned and when debugging I can see that the Index() method is successfully hit even though the user is not authenticated.
Am I doing something wrong? Testing at the wrong level? Should I rather be testing at the route level for this kind of thing?
I know that the [Authorize] attribute is working, because when I spin up the page, the login screen is indeed forced upon me - but how do I verify this in a test?
The controller and index method are very simple just so that I can verify the behaviour. I've included them for completeness:
[Authorize]
public class MyAdminController : Controller
{
public ActionResult Index()
{
return View();
}
}
Any help appreciated...
You are testing at the wrong level. The [Authorize] attribute ensures that the routing engine will never invoke that method for an unauthorized user - the RedirectResult will actually be coming from the route, not from your controller method.
Good news is - there's already test coverage for this (as part of the MVC framework source code), so I'd say you don't need to worry about it; just make sure your controller method does the right thing when it gets called, and trust the framework not to call it in the wrong circumstances.
EDIT: If you want to verify the presence of the attribute in your unit tests, you'll need to use reflection to inspect your controller methods as follows. This example will verify the presence of the Authorize attribute on the ChangePassword POST method in the 'New ASP.NET MVC 2 Project' demo that's installed with MVC2.
[TestFixture]
public class AccountControllerTests {
[Test]
public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() {
var controller = new AccountController();
var type = controller.GetType();
var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method");
}
}
Well you might be testing at the wrong level but its the test that makes sense. I mean, if I flag a method with the authorize(Roles="Superhero") attribute, I don't really need a test if I flagged it. What I (think I) want is to test that an unauthorized user doesn't have access and that an authorized user does.
For a unauthorized user a test like this:
// Arrange
var user = SetupUser(isAuthenticated, roles);
var controller = SetupController(user);
// Act
SomeHelper.Invoke(controller => controller.MyAction());
// Assert
Assert.AreEqual(401,
controller.ControllerContext.HttpContext.Response.StatusCode, "Status Code");
Well, it's not easy and it took me 10 hours, but here it is. I hope someone can benefit from it or convince me to go into another profession. :) (BTW - I'm using rhino mock)
[Test]
public void AuthenticatedNotIsUserRole_Should_RedirectToLogin()
{
// Arrange
var mocks = new MockRepository();
var controller = new FriendsController();
var httpContext = FakeHttpContext(mocks, true);
controller.ControllerContext = new ControllerContext
{
Controller = controller,
RequestContext = new RequestContext(httpContext, new RouteData())
};
httpContext.User.Expect(u => u.IsInRole("User")).Return(false);
mocks.ReplayAll();
// Act
var result =
controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Index");
var statusCode = httpContext.Response.StatusCode;
// Assert
Assert.IsTrue(result, "Invoker Result");
Assert.AreEqual(401, statusCode, "Status Code");
mocks.VerifyAll();
}
Although, thats not very useful without this helper function:
public static HttpContextBase FakeHttpContext(MockRepository mocks, bool isAuthenticated)
{
var context = mocks.StrictMock<HttpContextBase>();
var request = mocks.StrictMock<HttpRequestBase>();
var response = mocks.StrictMock<HttpResponseBase>();
var session = mocks.StrictMock<HttpSessionStateBase>();
var server = mocks.StrictMock<HttpServerUtilityBase>();
var cachePolicy = mocks.Stub<HttpCachePolicyBase>();
var user = mocks.StrictMock<IPrincipal>();
var identity = mocks.StrictMock<IIdentity>();
var itemDictionary = new Dictionary<object, object>();
identity.Expect(id => id.IsAuthenticated).Return(isAuthenticated);
user.Expect(u => u.Identity).Return(identity).Repeat.Any();
context.Expect(c => c.User).PropertyBehavior();
context.User = user;
context.Expect(ctx => ctx.Items).Return(itemDictionary).Repeat.Any();
context.Expect(ctx => ctx.Request).Return(request).Repeat.Any();
context.Expect(ctx => ctx.Response).Return(response).Repeat.Any();
context.Expect(ctx => ctx.Session).Return(session).Repeat.Any();
context.Expect(ctx => ctx.Server).Return(server).Repeat.Any();
response.Expect(r => r.Cache).Return(cachePolicy).Repeat.Any();
response.Expect(r => r.StatusCode).PropertyBehavior();
return context;
}
So that gets you confirmation that users not in a role don't have access. I tried writing a test to confirm the opposite, but after two more hours of digging through mvc plumbing I will leave it to manual testers. (I bailed when I got to the VirtualPathProviderViewEngine class. WTF? I don't want anything to do a VirtualPath or a Provider or ViewEngine much the union of the three!)
I am curious as to why this is so hard in an allegedly "testable" framework.
Why not just use reflection to look for the [Authorize] attribute on the controller class and / or the action method you are testing? Assuming the framework does make sure the Attribute is honored, this would be the easiest thing to do.
I don't agree with Dylan's answer, because 'user must be logged in' does not imply that 'controller method is annotated with AuthorizeAttribute'
to ensure 'user must be logged in' when you call the action method, the ASP.NET MVC framework does something like this (just hold on, it will get simpler eventually)
let $filters = All associated filter attributes which implement
IAuthorizationFilter
let $invoker = instance of type ControllerActionInvoker
let $ctrlCtx = instance or mock of type ControllerContext
let $actionDesc = instance or mock of type ActionDescriptor
let $authzCtx = $invoker.InvokeAuthorizationFilters($ctrlCtx, $filters, $actionDesc);
then controller action is authorized when $authzCtx.Result is not null
It is hard to implement this pseudo script in a working c# code. Likely, Xania.AspNet.Simulator makes it really simple to setup a test like this and performs exactly these step under the cover. here is an example.
first install the package from nuget (version 1.4.0-beta4 at the time of writing)
PM > install-package Xania.AspNet.Simulator -Pre
Then your test method could look like this (assuming NUnit and FluentAssertions are installed):
[Test]
public void AnonymousUserIsNotAuthorized()
{
// arrange
var action = new ProfileController().Action(c => c.Index());
// act
var result = action.GetAuthorizationResult();
// assert
result.Should().NotBeNull();
}
[Test]
public void LoggedInUserIsAuthorized()
{
// arrange
var action = new ProfileController().Action(c => c.Index())
// simulate authenticated user
.Authenticate("user1", new []{"role1"});
// act
var result = action.GetAuthorizationResult();
// assert
result.Should().BeNull();
}
For .NET Framework we use this class to verify that every MVC and API Controller have AuthorizeAttribute and that every API Controller should have a RoutePrefixAttribute.
[TestFixture]
public class TestControllerHasAuthorizeRole
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Test]
public void MvcControllersShouldHaveAuthrorizeAttribute()
{
var controllers = GetChildTypes<Controller>();
foreach (var controller in controllers)
{
var authorizeAttribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Mvc.AuthorizeAttribute), true) as System.Web.Mvc.AuthorizeAttribute;
Assert.IsNotNull(authorizeAttribute, $"MVC-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.AuthorizeAttribute), true) as System.Web.Http.AuthorizeAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveRoutePrefixAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.RoutePrefixAttribute), true) as System.Web.Http.RoutePrefixAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement RoutePrefixAttribute");
Assert.IsTrue(attribute.Prefix.StartsWith("api/", StringComparison.OrdinalIgnoreCase), $"API-controller {controller.FullName} does not have a route prefix that starts with api/");
}
}
}
It is a bit easier in .NET Core and .NET 5<. Here a MVC Controller inherits from Controller that in turn inherits from ControllerBase. An Api Controller inherits directly from ControllerBase and therefore we can test MVC and API Controllers using a single method:
public class AuthorizeAttributeTest
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Fact]
public void ApiAndMVCControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ControllerBase>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute), true) as Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
Assert.NotNull(attribute);
}
}
}

Unit Test Relies on UserManager and RoleManager

I am trying to unit test some of my methods that rely on UserManager and RoleManager and am having some difficulty.
Should I mock the UserManager and RoleManager and then pass it to the AdminController? or should I first access the AccountController's default SignIn action and authenticate. I am unsure how to do both options or what the best way to approach this is.
When not authenticating / instantiating the managers I get NullReferenceExceptions on the UserManager
My test
[Test]
public void MaxRole_SuperAdmin()
{
var adminController = new AdminController();
var maxRole = adminController.GetMaxRole(SuperAdminUserId);
Assert.AreEqual(maxRole, "Super Admin");
}
Controller and Method
[Authorize(Roles = "APGame Admin, APGame Investigator")]
[RequireHttps]
public class AdminController : Controller
{
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get { return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
private set { _userManager = value; }
}
private ApplicationRoleManager roleManager;
public ApplicationRoleManager RoleManager
{
get
{
return roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
}
private set { roleManager = value; }
}
public string GetMaxRole(string userId)
{
IEnumerable<string> userRoles = UserManager.GetRoles(userId);
string role = null;
if (userRoles.Contains("APGame Admin"))
{
if (userRoles.Contains("TeVelde Group") && userRoles.Contains("Genomics Group"))
role = "Super Admin";
else role = "Admin";
}
else if (userRoles.Contains("APGame Investigator"))
{
role = "Investigator";
}
else if (userRoles.Contains("APGame User"))
{
role = "User";
}
else
{
//TODO: Log no role, HIGH
}
return role;
}
}
If you followed my blog-post, you should've got something like this for constructor of ApplicationUserManager:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
{
// configuration-blah-blah
}
}
and your controller should have user manager object injected into constructor:
public class AdminController : Controller
{
private readonly ApplicationUserManager userManager;
public AdminController(ApplicationUserManager userManager)
{
this.userManager = userManager;
}
}
Now for your test - you need a mocking framework. I few years back I used to put MOQ in every single test, now mocking framework of preference is NSubstitue because of more readable syntax. Current time I very rarely use mocking substitutes and prefer integration tests, even to dip into a DB, but this is not an objective for this question/discussion.
So for you test you need to create your System Under Test (SUT) - AdminController:
[Test]
public void MaxRole_SuperAdmin()
{
var userStoreStub = NSubstitute.Substitute.For<IUserStore<ApplicationUser>>();
var userManager = new ApplicationUserManager(userStoreStub);
var sut = new AdminController(userManager);
//TODO set up method substitutions on userStoreStub - see NSubstitute documentation
var maxRole = sut.GetMaxRole(SuperAdminUserId);
Assert.AreEqual(maxRole, "Super Admin");
}
But this is very clumsy test. You are trying to test stuff that is too deep down. I'd recommend you move GetMaxRole record off from controller into a separate class - a service class, or this can be a part of ApplicationUserManager or can be a QueryHandler if you prefer. Whatever you call it, it should not really be part of a controller. I think this method actually belongs to ApplicationUserManager. This way your testing layers is reduced by one and you only really need to create user manager class, not the controller.
Given that GetMaxRole() method is part of ApplicationUserManager your test will become smaller:
[Test]
public void MaxRole_SuperAdmin()
{
var userStoreStub = NSubstitute.Substitute.For<IUserStore<ApplicationUser>>();
var sut = new ApplicationUserManager(userStoreStub);
//TODO set up method substitutions on userStoreStub - see NSubstitute documentation
var maxRole = sut.GetMaxRole(SuperAdminUserId);
Assert.AreEqual(maxRole, "Super Admin");
}
Reasons to move the tested method out from controller are as follows:
Over time I find that controllers are prone to be changed often with new dependencies added/removed/replaced. And if you have a few tests around controllers, you will have to change every test changed dependencies are used.
If you are to use GetMaxRole method some other place outside AdminController, you are in trouble. Controllers are not meant to share methods other than provide HTTP(S) endpoints.
Logic of your method look like a Domain Logic or Business Logic. This should be tested thoroughly. Controllers are not easy to test because they deal with HTTP requests and HttpContext which is not simple to test (though possible). Also it is best to avoid controller stuff mixed with Business Logic - helps you avoiding spaghetti code syndrome.
I can go on forever about this topic, but best read DI book by Mark Seeman and Unit Testing book by Roy Osherove.
You should mock the UserManager and the RoleManager and pass them to the AdminController

Categories