如何测试使用只读 属性 标识的控制器?

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

我有以下代码:

[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");
    }
}

我在测试这个 Index Page 时遇到问题。

User 是来自 ControllerBaseproperty,类型为 ClaimsPrincipal

我使用了一个 wrapper 来包装 usermanager,然后使用一个 interface 我会 mock

这种方法的问题是我无法将此 ClaimsPrincipal 设置为 null,因为它是 read-only.

这是我的测试:

[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;
    }
}

}

我愿意使用一种全新的方法。

我以前在identity上测试就是这样,现在卡住了。

查看ControllerBase的源代码,我们可以看到User是这样定义的

public ClaimsPrincipal User => HttpContext?.User;

所以User实际上来自HttpContext。但是 HttpContext 也是只读的。但是,深入挖掘源代码,我们可以看到 HttpContext 派生自 ControllerContext

public HttpContext HttpContext => ControllerContext.HttpContext;

唉! ControllerContext在具体实现中其实有一个setter!

public ControllerContext ControllerContext { get; set; }

如果需要,我们可以在这里设置一个全新的 ControllerContext。但我们真的只需要ControllerContext.User。幸运的是,它也有一个 setter。因为你真的只需要设置用户,我们可以在这里直接这样做而不是新建另一个 ControllerContext。

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