Home > Back-end >  How to mock AuthenticationStateProvider
How to mock AuthenticationStateProvider

Time:10-12

I have a class that takes an AuthenticationStateProvider in the constructor. I'd like to write unit test and mock this.

I'd like to be able to set what user is returned from the call GetAuthenticationStateAsync.

  const string userId = "123";
  const string userName = "John Doe";
  const string email = "[email protected]";

  var claims = new List<Claim>
  {
    new Claim(ClaimTypes.NameIdentifier, userId),
    new Claim(ClaimTypes.Name, userName),
    new Claim(ClaimTypes.Email, email),
 };

 var identity = new Mock<ClaimsIdentity>(claims);
 var principal = new Mock<ClaimsPrincipal>(identity.Object);

 var mockOfAuthenticationStateProvider = new Mock<AuthenticationStateProvider>();
 var mockOfAuthState = new Mock<AuthenticationState>();
          
 mockOfAuthenticationStateProvider.Setup(p => 
   p.GetAuthenticationStateAsync()).Returns(Task.FromResult(mockOfAuthState.Object)); 

I get this errormessage:

Testfunction threw exception: System.ArgumentException: Can not instantiate proxy of class: Microsoft.AspNetCore.Components.Authorization.AuthenticationState. Could not find a parameterless constructor. (Parameter 'constructorArguments') ---> System.MissingMethodException: Constructor on type 'Castle.Proxies.AuthenticationStateProxy' not found.

CodePudding user response:

AuthenticationStateProvider is abstract, so if you can't mock it you can create an implementation of it, like this:

public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly ClaimsPrincipal _principal;

    public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
    {
        _principal = principal;
    }

    // This static method isn't really necessary. You could call the 
    // constructor directly. I just like how it makes it more clear
    // what the fake is doing within the test.
    public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
    {
        return new FakeAuthenticationStateProvider(principal);
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        return Task.FromResult(new AuthenticationState(_principal));
    }
}

You can set it up like this:

const string userId = "123";
const string userName = "John Doe";
const string email = "[email protected]";
var claims = new List<Claim>
{
    new Claim(ClaimTypes.NameIdentifier, userId),
    new Claim(ClaimTypes.Name, userName),
    new Claim(ClaimTypes.Email, email),
};

// These don't need to be mocks. If they are the test likely
// won't behave correctly.
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
var authenticationStateProvider = 
    FakeAuthenticationStateProvider.ForPrincipal(principal);

and then pass it to any other class that depends on AuthenticationStateProvider.

When we create a fake implementation instead of a mock it's reusable and also easier to set up, so it keeps the test a little bit smaller.

For example, if the reason you're setting this up is so that the mock/fake returns a user with certain claims, you could have the fake just take a set of claims in its constructor. Then the constructor does the rest of the work, making your test even smaller.

For example,

public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly ClaimsPrincipal _principal;

    public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
    {
        _principal = principal;
    }

    public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
    {
        return new FakeAuthenticationStateProvider(principal);
    }

    public static FakeAuthenticationStateProvider ThatReturnsClaims(params Claim[] claims)
    {
        var identity = new ClaimsIdentity(claims);
        var principal = new ClaimsPrincipal(identity);
        return new FakeAuthenticationStateProvider(principal);
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        return Task.FromResult(new AuthenticationState(_principal));
    }
}

Now your test can have less clutter:

var claims = new []
{
    new Claim(ClaimTypes.NameIdentifier, "123"),
    new Claim(ClaimTypes.Name, "John Doe"),
    new Claim(ClaimTypes.Email, "[email protected]"),
};

var authenticationStateProvider = FakeAuthenticationStateProvider.ThatReturnsClaims(claims);

I usually end up with a folder called "Fakes" in my test project for them.

  • Related