我有一个具有以下签名的控制器:
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private ILogger<UsersController> _logger;
private readonly UserManager<IdentityUser> _usermanager;
public UsersController(ILogger<UsersController> logger, UserManager<IdentityUser> usermanager)
{
_usermanager = usermanager;
_logger = logger;
}
[HttpGet("{_uniqueid}")]
public async Task<ObjectResult> GetUser(string _uniqueid)
{
//Retrieve the object
try
{
var user = await _usermanager.FindByIdAsync(uniqueid);
var model = JsonConvert.DeserializeObject<GetUserModel>(user.ToString());
return new ObjectResult(JsonConvert.SerializeObject(model));
}
catch(CustomIdentityNotFoundException e)
{
return new BadRequestObjectResult(("User not found: {0}", e.Message));
}
}
}
现在我的单元测试如下:
public class UsersUnitTests
{
public UsersController _usersController;
private UserManager<IdentityUser> _userManager;
public UsersUnitTests()
{
_userManager = new MoqUserManager<IdentityUser>();
_usersController = new UsersController((new Mock<ILogger<UsersController>>()).Object, _userManager);
}
[Fact]
public async Task GetUser_ReturnsOkObjectResult_WhenModelStateIsValid()
{
//Setup
//Test
ObjectResult response = await _usersController.GetUser("realuser");
//Assert
//Should receive 200 and user data content body
response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK);
response.Value.Should().NotBeNull();
}
}
和Moq'd类:
public class MoqUserManager<T> : UserManager<IdentityUser>
{
public MoqUserManager(IUserStore<IdentityUser> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher, IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<IdentityUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public MoqUserManager()
: base((new MoqUserStore().Store), new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<IdentityUser>>().Object, new Mock<IEnumerable<IUserValidator<IdentityUser>>>().Object,
new Mock<IEnumerable<IPasswordValidator<IdentityUser>>>().Object, new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object, new Mock<IServiceProvider>().Object, new Mock<ILogger<UserManager<IdentityUser>>>().Object)
{
}
}
public class MoqUserStore : IdentityUserStore
{
private Mock<IdentityUserStore> _store;
public MoqUserStore()
:base(new Mock<IdentityDbContext>().Object, new Mock<ILogger<IdentityUserStore>>().Object, null)
{
_store = new Mock<IdentityUserStore>(new Mock<IdentityDbContext>().Object, new Mock<ILogger<IdentityUserStore>>().Object, null);
_store.Setup(x => x.FindByIdAsync("realuser", default(CancellationToken))).Returns(Task.Run(() => new IdentityUser("realuser")));
_store.Setup(x => x.FindByIdAsync("notrealuser", default(CancellationToken))).Throws(new CustomIdentityNotFoundException());
_store.Setup(x => x.CreateAsync(new IdentityUser("realuser"), default(CancellationToken))).Returns(Task.Run(() => IdentityResult.Success));
}
public IdentityUserStore Store { get => _store.Object; }
}
调用reference not set to an instance of an object
构造函数时出现MoqUserManager
错误。
我的问题是:对依赖于UserManager
和/或{的这些类型的控制器进行单元测试的最佳实践是什么(我会适应的工作,但会发臭到高高的天堂) {1}},模拟SignInManager
依赖关系的简便方法是什么?
答案 0 :(得分:1)
我考虑了DI模型和控制器的依赖关系。我只需要使用UserManager
中的几种方法,因此我的理论是删除{{1}中对UserManager
的依赖,并用实现我与{{ 1}}。让我们调用该接口UsersController
:
UserManager
接下来,我需要创建一个既从IMYUserManager
派生又实现public interface IMYUserManager
{
Task<IdentityUser> FindByIdAsync(string uniqueid);
Task<IdentityResult> CreateAsync(IdentityUser IdentityUser);
Task<IdentityResult> UpdateAsync(IdentityUser IdentityUser);
Task<IdentityResult> DeleteAsync(IdentityUser result);
}
的类。这里的想法是从接口实现方法将简单地成为派生类的替代,这样我就可以绕过UserManager
(和其余的)被标记为扩展方法并需要包装在静态类中。这是IMYUserManager
:
FindByIdAsync
快到家了。接下来,我自然更新了MyUserManager
以使用public class MYUserManager : UserManager<IdentityUser>, IMYUserManager
{
public MYUserManager(IUserStore<IdentityUser> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher, IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<IdentityUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public override Task<IdentityUser> FindByIdAsync(string userId)
{
return base.FindByIdAsync(userId);
}
//Removed other overridden methods for brevity; They also call the base class method
}
界面:
UsersController
自然地,在那之后,我必须使所有希望饱餐一顿的人都可以将此依赖项提供给服务容器:
IMYUserManager
最后,在确认实际上是 build 后,我更新了测试类:
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private ILogger<UsersController> _logger;
private readonly IMYUserManager _usermanager;
public UsersController(ILogger<UsersController> logger, IMYUserManager
usermanager)
{
_usermanager = usermanager;
_logger = logger;
}
}
几件事:
从public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMYUserManager, MYUserManager>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
删除对public class UsersControllerTests
{
public UsersController _usersController;
private Mock<IMYUserManager> _userManager;
public UsersControllerTests()
{
_userManager = new Mock<IMYUserManager>();
_usersController = new UsersController((new Mock<ILogger<UsersController>>
()).Object, _userManager.Object);
}
[Fact]
public async Task GetUser_ReturnsOkObjectResult_WhenModelStateIsValid()
{
//Setup
_userManager.Setup(x => x.FindByIdAsync("realuser"))
.Returns(Task.Run(() => new IdentityUser("realuser","realuser1")));
_usersController.ModelState.Clear();
//Test
ObjectResult response = await _usersController.GetUser("realuser");
//Assert
//Should receive 200 and user data content body
response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK);
response.Value.Should().NotBeNull();
}
}
的依赖关系与DI模型保持一致。提取依赖关系(因此提取实现细节,例如扩展方法),并使它们不仅可被模拟,而且可用于整个UserManager
,这意味着当我需要实现另一种方法时,我只有3个非常简单的步骤对于用户经理:
UsersController
IServiceCollection
中调用基类实现我可能会重新考虑服务范围,我选择IMYUserManager
只是为了证明概念,但是性能和业务要求将决定是否保持不变。