我在研究测试方法和测试方法时遇到了问题。
我有一个注入UserManager
的控制器,并调用CreateAsync
方法来创建新用户。
我不想测试Identity用户管理器,因为这已经经过了彻底的测试。我想做的是测试控制器是否运行正确的路径(在我的情况下,有3条路径,发送响应模型状态错误,身份响应错误或简单字符串)
我是否应该尝试创建用户管理器的模拟以创建我的测试(我不确定如何将用户管理器设置为模拟依赖项) 其次,我如何设置条件来验证控制器是否采用了给定的路径。
我正在使用 xUnit 和 Moq 。
[Route("api/[controller]")]
public class MembershipController : BaseApiController
{
private UserManager<ApplicationUser> _userManager;
public MembershipController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpGet("RegisterNewUser")]
public HttpResponseMessage RegisterNewUser([FromBody] NewUserRegistration user)
{
if (ModelState.IsValid)
{
ApplicationUser newUser = new ApplicationUser();
newUser.UserName = user.username;
newUser.Email = user.password;
IdentityResult result = _userManager.CreateAsync(newUser, user.password).Result;
if (result.Errors.Count() > 0)
{
var errors = new IdentityResultErrorResponse().returnResponseErrors(result.Errors);
return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
}
}
else
{
var errors = new ViewModelResultErrorResponse().returnResponseErrors(ModelState);
return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
}
return this.WebApiResponse(
"We have sent a valifation email to you, please click on the verify email account link.",
HttpStatusCode.OK);
}
}
在我的单元测试中,我有以下测试快乐路径方案
[Fact]
public void RegisterNewUser_ReturnsHttpStatusOK_WhenValidModelPosted()
{
var mockStore = new Mock<IUserStore<ApplicationUser>>();
var mockUserManager = new Mock<UserManager<ApplicationUser>>(mockStore.Object, null, null, null, null, null, null, null, null);
ApplicationUser testUser = new ApplicationUser { UserName = "user@test.com" };
mockStore.Setup(x => x.CreateAsync(testUser, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(IdentityResult.Success));
mockStore.Setup(x => x.FindByNameAsync(testUser.UserName, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(testUser));
mockUserManager.Setup(x => x.CreateAsync(testUser).Result).Returns(new IdentityResult());
MembershipController sut = new MembershipController(mockUserManager.Object);
var input = new NewUserInputBuilder().Build();
sut.RegisterNewUser(input);
}
sut.RegisterNewUser(input)中的“input”;指的是一个辅助类,它构造了控制器动作所需的视图模型:
public class NewUserInputBuilder
{
private string username { get; set; }
private string password { get; set; }
private string passwordConfirmation { get; set; }
private string firstname { get; set; }
private string lastname { get; set; }
internal NewUserInputBuilder()
{
this.username = "user@test.com";
this.password = "password";
this.passwordConfirmation = "password";
this.firstname = "user";
this.lastname = "name";
}
internal NewUserInputBuilder WithNoUsername()
{
this.username = "";
return this;
}
internal NewUserInputBuilder WithMisMatchedPasswordConfirmation()
{
this.passwordConfirmation = "MismatchedPassword";
return this;
}
internal NewUserRegistration Build()
{
return new NewUserRegistration
{ username = this.username, password = this.password,
passwordConfirmation = this.passwordConfirmation,
firstname = this.firstname, lastname = this.lastname
};
}
}
我的目标是通过测试强制实施3个条件:
使用返回json对象的抽象类处理错误
控制器的基类只构造一个HttpResponseMessage
来返回。
基本上我想通过强制测试降低模型状态错误路径,identityresult.errors路径以及可以实现快乐路径来检查是否调用了正确的错误响应类。
然后我的计划是单独测试错误响应类。
希望这足够详细。
答案 0 :(得分:2)
正在测试的Mehod应该是异步的,而不是使用阻塞调用,即.Result
[HttpGet("RegisterNewUser")]
public async Task<HttpResponseMessage> RegisterNewUser([FromBody] NewUserRegistration user) {
if (ModelState.IsValid) {
var newUser = new ApplicationUser() {
UserName = user.username,
Email = user.password
};
var result = await _userManager.CreateAsync(newUser, user.password);
if (result.Errors.Count() > 0) {
var errors = new IdentityResultErrorResponse().returnResponseErrors(result.Errors);
return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
}
} else {
var errors = new ViewModelResultErrorResponse().returnResponseErrors(ModelState);
return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
}
return this.WebApiResponse(
"We have sent a valifation email to you, please click on the verify email account link.",
HttpStatusCode.OK);
}
审核快乐路径方案和测试方法表明,无需设置 UserStore
,因为测试将直接覆盖用户管理器虚拟成员。
注意,测试也是异步的。
- 创建有效的viewmodel并返回成功消息
醇>
[Fact]
public async Task RegisterNewUser_ReturnsHttpStatusOK_WhenValidModelPosted() {
//Arrange
var mockStore = Mock.Of<IUserStore<ApplicationUser>>();
var mockUserManager = new Mock<UserManager<ApplicationUser>>(mockStore, null, null, null, null, null, null, null, null);
mockUserManager
.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(IdentityResult.Success);
var sut = new MembershipController(mockUserManager.Object);
var input = new NewUserInputBuilder().Build();
//Act
var actual = await sut.RegisterNewUser(input);
//Assert
actual
.Should().NotBeNull()
.And.Match<HttpResponseMessage>(_ => _.IsSuccessStatusCode == true);
}
- 创建有效的viewmodel但返回转换后的IdentityResponse错误(例如用户存在)
醇>
为此,您只需设置模拟以返回错误的结果。
[Fact]
public async Task RegisterNewUser_ReturnsHttpStatusBadRequest_WhenViewModelPosted() {
//Arrange
//...code removed for brevity
mockUserManager
.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "test"}));
//...code removed for brevity
//Assert
actual
.Should().NotBeNull()
.And.Match<HttpResponseMessage>(_ => _.StatusCode == HttpStatusCode.BadRequest);
}
而
- 创建无效的viewmodel并返回Modelstate errors
醇>
您只需要设置控制器的模型状态,使其无效。
[Fact]
public async Task RegisterNewUser_ReturnsHttpStatusBadRequest_WhenInvalidModelState() {
//Arrange
var mockStore = Mock.Of<IUserStore<ApplicationUser>>();
var mockUserManager = new Mock<UserManager<ApplicationUser>>(mockStore, null, null, null, null, null, null, null, null);
var sut = new MembershipController(mockUserManager.Object);
sut.ModelState.AddModelError("", "invalid data");
var input = new NewUserInputBuilder().Build();
//Act
var actual = await sut.RegisterNewUser(input);
//Assert
actual
.Should().NotBeNull()
.And.Match<HttpResponseMessage>(_ => _.StatusCode == HttpStatusCode.BadRequest);
}
FluentAssertions 用于执行所有断言。您可以轻松使用Assert.*
API。
这应该足以让你顺利解决上述问题。
答案 1 :(得分:0)
如果不想测试用户管理器,这是使用NUnit的一种简单方法(您可以使用xUnit做类似的事情)。 (我还展示了如何使用可用于设置模拟数据的内存数据库将DbContext传递到同一控制器)
private DbContextOptions<MyContextName> options;
[OneTimeSetUp]
public void SetUp()
{
options = new DbContextOptionsBuilder<MyContextName>()
.UseInMemoryDatabase(databaseName: "MyDatabase")
.Options;
// Insert seed data into the in-memory mock database using one instance of the context
using (var context = new MyContextName(options))
{
var testWibble = new Wibble { MyProperty = 1, MyOtherProperty = 2 ... };
context.wibbles.Add(testWibble);
context.SaveChanges();
}
}
[Test]
public void Some_TestMethod()
{
// Use a clean instance of the context to run the test
using (var context = new MyDbContext(options))
{
var store = new UserStore<MyUserType>(context);
var userManager = new UserManager<MyUserType>(store, null, null, null, null, null, null, null, null);
MyController MyController = new MyController(userManager, context);
... test the controller
}
}