How to mock authorize attribute for testing in asp.net core 3.1

Why not making it possible to by pass Authorization in development stage in general, so both integration testing and developer can do the development with less frustration?

Bypass Authorization

It is often required to make integration test for your project. In many cases your controllers has Authorize attribute.

There are different strategies and ways make integration test, it all depends on project size and development stages. So in this post, I will demonstrate how to by pass Authorize attribute for testing purpose.

For this example I have created default template Weather forecast application. The only thing I did here is adding Authorize attribute.

[ApiController]
[Authorize]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    ......
}

And I have also added following class libraries:

  • Microsoft.AspNetCore.Authentication
  • Microsoft.AspNetCore.Authentication.JwtBearer

So in our project we have 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 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 lets create a FakePolicyEvaluator class and added it to our folder called Testing in our Weather forecast proejct. In our FakePolicyEvaluator we will define the identity, claims etc.

So let 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 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 return 5 objects, lets 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 what you need. In my cases trying re-login, re-authenticate or any kind that disturb the main business of development will give more frustration.

Lets assume we have following stages:

  • Development
  • Test
  • UAT
  • Production

In my glasses, development is the stage where developer working directly with visual studio on given project. Why not making it possible to by pass Authorization in development stage in general, so both integration testing and developer can do the development with less frustration?

Now we did the 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 Weather forecast project and add our fake class to it is only covering development environment.

We inject IWebHostEnvironmentinterface 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 development environment. We can both run unit testing for integration test and execute the software locally with out need of Authorization.

Download the source code for this example from GitHub.