I have a ASP.NET Core MVC API with controllers that need to be unit tested.
Controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace TransitApi.Api.Controllers
{
[Route("api/foo")]
public class FooController : Controller
{
private IFooRepository FooRepository { get; }
public FooController(IFooRepository fooRepository)
{
FooRepository = fooRepository;
}
[HttpGet]
[Authorize("scopes:getfoos")]
public async Task<IActionResult> GetAsync()
{
var foos = await FooRepository.GetAsync();
return Json(foos);
}
}
}
It is essential that I am able to unit test the effectiveness of the AuthorizeAttribute
. We have had issues in our code base with missing attributes and incorrect scopes. This answer is exactly what I am looking for, but not having a ActionInvoker
method in Microsoft.AspNetCore.Mvc.Controller
means I am not able to do it this way.
Unit Test:
[Fact]
public void GetAsync_InvalidScope_ReturnsUnauthorizedResult()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
// User unfortunately not available in HttpContext
//,User = new User() { Scopes = "none" }
}
};
// Act
var result = controller.GetAsync().Result;
// Assert
Assert.IsType<UnauthorizedResult>(result);
}
How can I unit test that users without the correct scopes are denied access to my controller method?
Currently I have settled for testing merely the presence of an AuthorizeAttribute
as follows, but this is really not good enough:
[Fact]
public void GetAsync_Analysis_HasAuthorizeAttribute()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
}
};
// Act
var type = controller.GetType();
var methodInfo = type.GetMethod("GetAsync", new Type[] { });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
// Assert
Assert.True(attributes.Any());
}
AuthorizeAttribute
not good enough?AuthorizeAttribute
is both an attribute and anIAuthorizationFilter
. The attribute part doesn't do anything, it is just meta-data. MVC's unit tests guarantee that if it is present, it will be registered as an authorization filter for the current request and the logic run. If you were using a subclass ofAuthorizeAttribute
, then it would make sense to test its logic, but since you are not the only thing you need to test is the presence of the attribute and configuration of its properties (Users
andGroups
). – ChuckchuckfullAuthorizeAttribute
at startup, and then useAllowAnonymous
to override the behavior. That way, they are locked down by default and you won't need to worry about a later change missing one. Alternatively, you could create your own customIAuthorizationFilter
registered globally that manages the security for the entire application (and possibly even your own attributes to do certain things), which could then be tested as a separate piece than your controllers and actions. – Chuckchuckfullscope:bar
may getbar
s but notfoo
s and vice versa, and a user with ascope:all
can access both. That's partly why testing these attributes is so important. – Edgerton