Действия контроллера модульного тестирования 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 условия с помощью тестов:

  1. Создайте действительную модель представления и верните сообщение об успехе.
  2. Создайте действительную модель представления, но возвращает ошибку IdentityResponse (например, пользователь существует), которая преобразуется в
  3. Создать недопустимую модель представления и возвращает ошибки 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 В качестве теста будут переопределены виртуальные члены диспетчера пользователей напрямую.

Обратите внимание, что тест также был сделан асинхронным.

  1. Создайте действительную модель представления и верните сообщение об успехе.
[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);        
}
  1. Создайте действительную модель представления, но возвращает ошибку 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);
}

И для

  1. Создать недопустимую модель представления и возвращает ошибки 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
        }
    }
Другие вопросы по тегам