I am in the process of adding integration tests at work for an MVC app. Many of our endpoints have policies applied to them, e.g.
namespace WorkProject
{
[Route("A/Route")]
public class WorkController : Controller
{
[HttpPost("DoStuff")]
[Authorize(Policy = "CanDoStuff")]
public IActionResult DoStuff(){/* */}
}
}
For our integration tests, I have overridden the WebApplicationFactory
like it is suggested in the ASP .NET Core documentation. My goal was to overload the authentication step and to bypass the policy by making a class which allows all parties through the authorization policy.
namespace WorkApp.Tests
{
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
options.DefaultChallengeScheme = "Test Scheme";
}).AddTestAuth(o => { });
services.AddAuthorization(options =>
{
options.AddPolicy("CanDoStuff", policy =>
policy.Requirements.Add(new CanDoStuffRequirement()));
});
// I've also tried the line below, but neither worked
// I figured that maybe the services in Startup were added before these
// and that a replacement was necessary
// services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
services.Replace(ServiceDescriptor.Transient<IAuthorizationHandler, CanDoStuffActionHandler>());
});
}
}
internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
{
public CanDoStuffActionHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class CanDoStuffRequirement : IAuthorizationRequirement
{
}
}
The first thing that I do to the services is override the authentication as suggested here (without the bit about overriding Startup
since that didn't seem to work for me). I am inclined to believe that this authentication override works. When I run my tests, I receive an HTTP 403 from within the xUnit testing framework. If I hit the route that I am testing from PostMan I receive an HTTP 401. I have also made a class that lives in the custom web application factory that allows all requests for the CanDoStuff
authorization handler. I thought this would allow the integration tests through the authorization policy, but, as stated above, I receive an HTTP 403. I know that a 403 will be returned if the app doesn't know where certain files are. However, this is a post route strictly for receiving and processing data and this route does not attempt to return any views so this 403 is most likely related to the authorization policy which, for some reason, is not being overridden.
I'm clearly doing something wrong. When I run the test under debug mode and set a breakpoint in the HandleRequirementsAsync
function, the application never breaks. Is there a different way that I should be attempting to override the authorization policies?
Here is what I did.
WebApplicationFactory
with my own. Note, I still added my application's startup as the template parameterConfigureWebHost
function to use my custom startup class.ConfigureWebHost
function via builder.ConfigureServices
.builder.ConfigureServices
in the ConfigureWebHost
function.IAuthorizationHandler
for the policy that allows all requests to succeed.I hope I have done a decent job at explaining what I did. If not, hopefully the sample code below is easy enough to follow.
YourController.cs
namespace YourApplication
{
[Route("A/Route")]
public class WorkController : Controller
{
[HttpPost("DoStuff")]
[Authorize(Policy = "CanDoStuff")]
public IActionResult DoStuff(){/* */}
}
}
Test.cs
namespace YourApplication.Tests
{
public class Tests
: IClassFixture<CustomWebApplicationFactory<YourApplication.Startup>>
{
private readonly CustomWebApplicationFactory<YourApplication.Startup> _factory;
public Tests(CustomWebApplicationFactory<YourApplication.Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task SomeTest()
{
var client = _factory.CreateClient();
var response = await client.PostAsync("/YourEndpoint");
response.EnsureSuccessStatusCode();
Assert.Equal(/* whatever your condition is */);
}
}
}
CustomWebApplicationFactory.cs
namespace YourApplication.Tests
{
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
options.DefaultChallengeScheme = "Test Scheme";
}).AddTestAuth(o => { });
services.AddAuthorization(options =>
{
options.AddPolicy("CanDoStuff", policy =>
policy.Requirements.Add(new CanDoStuffRequirement()));
});
services.AddMvc().AddApplicationPart(typeof(YourApplication.Controllers.YourController).Assembly);
services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
});
builder.UseStartup<TestStartup>();
}
}
internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
{
public CanDoStuffActionHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class CanDoStuffRequirement : IAuthorizationRequirement
{
}
}
TestStartup.cs
namespace YourApplication.Tests
{
public class TestStartup : YourApplication.Startup
{
public TestStartup(IConfiguration configuration) : base(configuration)
{
}
protected override void ConfigureAuthServices(IServiceCollection services)
{
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With