如何测试使用只读 属性 标识的控制器?
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
是来自 ControllerBase
的 property
,类型为 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;
}
我有以下代码:
[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
是来自 ControllerBase
的 property
,类型为 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;
}