Действия контроллера модульного тестирования Asp.net Core Identity
У меня проблемы с разработкой, как и что тестировать.
У меня есть контроллер, который вводит 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(вход); ссылается на вспомогательный класс, который создает модель представления, для которой требуется действие контроллера:
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 условия с помощью тестов:
- Создайте действительную модель представления и верните сообщение об успехе.
- Создайте действительную модель представления, но возвращает ошибку IdentityResponse (например, пользователь существует), которая преобразуется в
- Создать недопустимую модель представления и возвращает ошибки Modelstate.
Ошибки обрабатываются с использованием абстрактного класса, который возвращает объект json. Базовый класс для контроллера просто создает HttpResponseMessage
для возвращения.
По сути, я хочу проверить, что правильный класс ответа об ошибке вызывается путем принудительного выполнения теста по пути ошибки состояния модели, пути identityresult.errors и того, что счастливый путь может быть достигнут.
Тогда мой план состоит в том, чтобы протестировать классы ответов на ошибки в изоляции.
Надеюсь, это достаточно подробно.
1 ответ
Тестируемый метод должен быть асинхронным и не использовать блокирующие вызовы, т.е. .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
В качестве теста будут переопределены виртуальные члены диспетчера пользователей напрямую.
Обратите внимание, что тест также был сделан асинхронным.
- Создайте действительную модель представления и верните сообщение об успехе.
[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);
}
- Создайте действительную модель представления, но возвращает ошибку 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);
}
И для
- Создать недопустимую модель представления и возвращает ошибки Modelstate.
Вам просто нужно установить состояние модели контроллера, чтобы оно было недействительным.
[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.
Этого должно быть достаточно, чтобы вы справились с вышеуказанным вопросом.
Вот простой способ использования 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
}
}