It is often required to make integration test for your project. In many cases, your controllers have to Authorize attributes.
There are different strategies and ways to make integration test, it all depends on project size and development stages. So in this post, I will demonstrate how to bypass Authorize attribute for testing purposes.
For this example, I have created a default template Weather forecast application. The only thing I did here was add Authorize attribute.
[ApiController]
[Authorize]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
......
}
And I have also added the following class libraries:
- Microsoft.AspNetCore.Authentication
- Microsoft.AspNetCore.Authentication.JwtBearer
So in our project we have the following item groups:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.4" />
</ItemGroup>
And create a xUnit test project with the following item groups:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
So far so good :).
Now let’s create a FakePolicyEvaluator class and added it to our folder called Testing in our Weather forecast project. In our FakePolicyEvaluator, we will define the identity, claims, etc.
So let’s go to our FakePolicyEvaluator
class:
public class FakePolicyEvaluator : IPolicyEvaluator
{
public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
var testScheme = "FakeScheme";
var principal = new ClaimsPrincipal();
principal.AddIdentity(new ClaimsIdentity(new[] {
new Claim("Permission", "CanViewPage"),
new Claim("Manager", "yes"),
new Claim(ClaimTypes.Role, "Administrator"),
new Claim(ClaimTypes.NameIdentifier, "John")
}, testScheme));
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal,
new AuthenticationProperties(), testScheme)));
}
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy,
AuthenticateResult authenticationResult, HttpContext context, object resource)
{
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
Now we go to our xUnit project, we create a testing class BypassAuthorizationTest
that implement IClassFixture<WebApplicationFactory<Startup>>
.
in our ConfigureTestServices
we added services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
So in your test code like this:
private readonly HttpClient _client;
public BypassAuthorizationTest(WebApplicationFactory<Startup> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
});
}).CreateClient();
}
And finally, let’s test it, we know that our weather forecast controller returns 5 objects, let’s make a simple it:
[Fact]
public async Task AuthorizedByPassed()
{
var response = (await _client.GetAsync("/weatherforecast")).EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<WeatherForecast[]>(stringResponse);
Assert.Equal(5, result.Length);
}
That is it. Now when you test, it will bypass authentication.
But is it enough, it depends on what you need. In my case trying re-login, re-authenticate or any kind that disturbs the main business of development will give more frustration.
Let’s assume we have the following stages:
- Development
- Test
- UAT
- Production
In my glasses, development is the stage where the developer works directly with the visual studio on a given project. Why not make it possible to bypass Authorization in the development stage in general, so both integration testing and developer can do the development with less frustration?
Now we did most of the job previously, we could reuse some of it for our next goal.
Lets’ change BypassAuthorizationTest
to:
_client = factory.CreateClient();
Now we go to our Startup.cs
in the Weather forecast project and adding our fake class to it is only covers the development environment.
We inject IWebHostEnvironment
interface in Startup
constructor:
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
Env = env;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Env { get; }
In our ConfigureServices
we added our fake class:
public void ConfigureServices(IServiceCollection services)
{
if (Env.IsDevelopment())
{
services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
}
services.AddControllers();
}
Now Authorization is automatically faked for a development environment. We can both run unit testing for integration tests and execute the software locally without the need for Authorization.
Download the source code for this example from GitHub.
Hello, sir,
I have a question, please.
I did implement the code that you posted and it worked for [Authorize] Attribute, which means my Integration Tests work now… but not all of them.
On some methods from my Controller, I have [Authorize(Roles=”admin”)], or [Authorize(Roles=”customer”)], which means that the user needs to be authenticated and authorized with the specified role.
The strange thing is that for some of the tests, that call these Controller methods, that require either admin or customer, for some it works and for others, it doesn’t.
Even stranger, in the FakePolicyEvaluator, where you add the Claims for the Roles, I haven’t even added the “admin” or the “customer” role. If I add them, I still have the same result.
Why is this happening? How could I solve it?