grozdeto
grozdeto

Reputation: 1379

How can I test a controller that uses identity with a readonly property?

I have the following code:

[Route("resources/avatar")]
[ApiController]
public class AvatarController : ControllerBase
{
    private readonly ApplicationDbContext database;
    private readonly IWebHostEnvironment environment;
    private readonly IUserManagerWrapper userManagerWrapper;

    public AvatarController(IUserManagerWrapper userManagerWrapper, IWebHostEnvironment environment,
        ApplicationDbContext database)
    {
        this.userManagerWrapper = userManagerWrapper;
        this.environment = environment;
        this.database = database;
    }

    [HttpGet]
    [Route("")]
    public async Task<IActionResult> Index()
    {
        if (User == null) return DefaultImage();

        var user = await this.userManagerWrapper.GetUserAsync(User);
        if ((user?.Avatar?.Length ?? 0) == 0) return DefaultImage();

        return File(user.Avatar, "image/jpeg");
    }
}

I have an issue with testing this Index Page.

User is a property that comes from ControllerBase and is of type ClaimsPrincipal.

I used a wrapper where I would wrap the usermanager and then use an interface that I would mock.

The problem with this approach is I cannot set this ClaimsPrincipal to null because it is a read-only.

This was my test:

[TestFixture]
public class AvatarController_Should
{
    [Test]
    public async Task IndexReturnsDefaultImage()
    {
        var hostingEnvironmentMock = new Mock<IWebHostEnvironment>();
        var dabataseName = nameof(IndexReturnsDefaultImage);
        var options = AvatarTestUtil.GetOptions(dabataseName);
        var userManagerWrapperMock = new Mock<IUserManagerWrapper>();

        using (var actAndAssertContext = new ApplicationDbContext(options))
        {
            var sut = new AvatarController(userManagerWrapperMock.Object, hostingEnvironmentMock.Object, actAndAssertContext);
        }
     }
}
   public class AvatarTestUtil
   {
    public static DbContextOptions<ApplicationDbContext> GetOptions(string databaseName)
    {
        var serviceCollection = new ServiceCollection()
            .AddEntityFrameworkInMemoryDatabase()
            .BuildServiceProvider();

        return new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName)
            .UseInternalServiceProvider(serviceCollection)
            .Options;
    }
}

}

I am open to using a completely new approach.

This was how I used to do test on the identity before, but I am stuck now.

Upvotes: 0

Views: 955

Answers (1)

user12447201
user12447201

Reputation:

Looking at the source code for ControllerBase, we can see User is defined as so

public ClaimsPrincipal User => HttpContext?.User;

So the User actually comes from HttpContext. But HttpContext is readonly as well. Digging into the source code deeper, though, we can see that HttpContext is derived from ControllerContext

public HttpContext HttpContext => ControllerContext.HttpContext;

Alas! ControllerContext actually has a setter in the concrete implementation!

public ControllerContext ControllerContext { get; set; }

We could set up a whole new ControllerContext here if we wanted. But we really just need ControllerContext.User. Luckily, that has a setter too. Since you only really need to set the User, we can do so directly here rather than newing up another ControllerContext.

using (var actAndAssertContext = new ApplicationDbContext(options))
{
     var sut = new AvatarController(userManagerWrapperMock.Object, hostingEnvironmentMock.Object, actAndAssertContext);
     sut.ControllerContext.HttpContext.User = null;
}

Upvotes: 2

Related Questions