Как получить значения из задачи <IActionResult>, возвращенные через API для модульного тестирования

Я создал API, используя ASP.NET MVC Core v2.1. Один из моих методов HttpGet настроен следующим образом:

public async Task<IActionResult> GetConfiguration([FromRoute] int? id)
{
    try
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        ..... // Some code here

        return Ok(configuration);
    }
    catch (Exception ex)
    {
        ... // Some code here
    }
}

При модульном тестировании я могу проверить, что ответом был Ok, но мне действительно нужно увидеть значения конфигурации. Кажется, я не могу заставить это работать со следующим:

[TestMethod] 
public void ConfigurationSearchGetTest()
{
    var context = GetContextWithData();
    var controller = new ConfigurationSearchController(context);
    var items = context.Configurations.Count();
    var actionResult = controller.GetConfiguration(12);

    Assert.IsTrue(true);
    context.Dispose();
}

Во время выполнения я могу проверить, что actionResult имеет определенные значения, для которых я не могу кодировать. Я что-то делаю не так? Или я просто неправильно об этом думаю? Я бы хотел уметь:

Assert.AreEqual(12, actionResult.Values.ConfigurationId);

Вам следует дождаться асинхронного метода.

mm8 11.06.2018 17:26

Надеюсь, в вашем контроллере не так много кода, который действительно нуждается в тестировании, и большая его часть находится в других, более тестируемых классах! Обратите внимание, что, поскольку вы используете ASP.NET MVC Core 2.1, вы можете вместо этого начать использовать тип ActionResult<T>. Это должно помочь в вашем тестировании.

DavidG 11.06.2018 17:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
28
2
37 010
5

Ответы 5

Вам нужно дождаться вызова GetConfiguration, чтобы вернуть объект IActionResult следующим образом:

var actionResult = await controller.GetConfiguration(12);

Для этого вам нужно также изменить сигнатуру вашего метода тестирования, чтобы она была асинхронной. Так что измените это:

public void ConfigurationSearchGetTest()

К этому:

public async Task ConfigurationSearchGetTest()

Хорошая практика предполагает, что у вас нет большого количества кода в действиях вашего контроллера для тестирования, а основная часть логики находится в разделенных объектах в другом месте, которые намного легче тестировать. Сказав это, если вы все еще хотите протестировать свои контроллеры, вам нужно сделать свой тест async и дождаться звонков.

Одна из проблем, с которыми вы столкнетесь, заключается в том, что вы используете IActionResult, поскольку он позволяет вам возвращать BadRequest(...) и Ok(...). Однако, поскольку вы используете ASP.NET MVC Core 2.1, вы можете вместо этого начать использовать новый тип ActionResult<T>. Это должно помочь в вашем тестировании, потому что теперь вы можете получить прямой доступ к строго типизированному возвращаемому значению. Например:

//Assuming your return type is `Configuration`
public async Task<ActionResult<Configuration>> GetConfiguration([FromRoute] int? id)
{
    try
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        ..... // Some code here

        // Note we are now returning the object directly, there is an implicit conversion 
        // done for you
        return configuration;
    }
    catch (Exception ex)
    {
        ... // Some code here
    }
}

Обратите внимание, что теперь мы возвращаем объект напрямую, так как есть неявное преобразование из Foo в ActionResult<Foo>

Теперь ваш тест может выглядеть так:

[TestMethod] 
public async Task ConfigurationSearchGetTest()
{
    var context = GetContextWithData();
    var controller = new ConfigurationSearchController(context);
    var items = context.Configurations.Count();

    // We now await the call
    var actionResult = await controller.GetConfiguration(12);

    // And the value we want is now a property of the return
    var configuration = actionResult.Value;

    Assert.IsTrue(true);
    context.Dispose();
}

Итак, это, похоже, частично сработало, но проверка actionResult.Value имеет значение null. actionResult.Result.Value.ConfigurationId можно увидеть во время выполнения, но я не могу его запрограммировать, поскольку он не существует.

Christopher J. Reynolds 11.06.2018 18:06

А, тогда вам, наверное, просто нужно вернуть configuration вместо Ok(configuration).

DavidG 11.06.2018 18:07

Да, думаю, я мог бы это сделать, но я искал способ получить ActionResult вместе с конфигурацией / конфигурациями, соответствующими запросам.

Christopher J. Reynolds 11.06.2018 18:56

Почему это не документировано лучше? На получение возвращенного значения у меня ушло несколько часов. Спасибо.

Goose 16.08.2018 16:23

@DavidG Большое спасибо за ваш комментарий, я застрял на этой глупой ошибке на час!

informaticienzero 10.12.2018 18:00

Вы можете получить протестированный контроллер без изменения возвращаемого типа. IActionResult - это базовый тип для всех остальных. Приведите результат к ожидаемому типу и сравните возвращаемое значение с ожидаемым.

Поскольку вы тестируете асинхронный метод, сделайте тестовый метод также асинхронным.

[TestMethod] 
public async Task ConfigurationSearchGetTest()
{
    using (var context = GetContextWithData())
    {
        var controller = new ConfigurationSearchController(context);
        var items = context.Configurations.Count();

        var actionResult = await controller.GetConfiguration(12);

        var okResult = actionResult as OkObjectResult;
        var actualConfiguration = okResult.Value as Configuration;

        // Now you can compare with expected values
        actualConfuguration.Should().BeEquivalentTo(expected);
    }
}

Для целей тестирования это кажется лучшим ответом

shelbypereira 11.10.2019 10:42

делает это var actualConfiguration = okResult.Value as Configuration; получить присвоить значения фактической конфигурации ??

Prageeth godage 04.06.2021 16:16

Поскольку моя репутация не позволяет мне комментировать ответ @DavidG, который идет в правильном направлении, я поставлю образец того, как получить значение внутри Task<IActionResult>.

Как указал @Christopher J. Reynolds, actionResult.Value можно увидеть на время выполнения, но не на сборник.

Итак, я покажу базовый тест, в котором получим Values:

[TestMethod]
public async Task Get_ReturnsAnArea()
{
    // Arrange
    string areaId = "SomeArea";
    Area expectedArea = new Area() { ObjectId = areaId, AreaNameEn = "TestArea" };

    var restClient = new Mock<IRestClient>();
    restClient.Setup(client => client.GetAsync<Area>(It.IsAny<string>(), false)).ReturnsAsync(expectedArea);

    var controller = new AreasController(restClient.Object);

    //// Act

    // We now await the call
    IActionResult actionResult = await controller.Get(areaId);

    // We cast it to the expected response type
    OkObjectResult okResult = actionResult as OkObjectResult;

    // Assert

    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);

    Assert.AreEqual(expectedArea, okResult.Value);

   // We cast Value to the expected type
    Area actualArea = okResult.Value as Area;
    Assert.IsTrue(expectedArea.AreaNameEn.Equals(actualArea.AreaNameEn));
}

Конечно, это можно улучшить, но я просто хотел показать вам простой способ получить это.

Я надеюсь, что это помогает.

Имеет ли смысл привести его к OkObjectResult, а затем проверить, действительно ли код состояния равен 200?

liqSTAR 26.05.2020 11:00

Если вам нужно быстрое решение, используйте JsonConvert.SerializeObject (), а после этого JsonConvert.DeserializeObject (), тогда вы получите объект со значениями.

 [TestMethod] 
    public async Task ConfigurationSearchGetTest()
    {
        using (var context = GetContextWithData())
        {
            var controller = new ConfigurationSearchController(context);
            var items = context.Configurations.Count();
    
            var actionResult = await controller.GetConfiguration(12);
    
            var okResult = actionResult as OkObjectResult;
            var actualConfiguration = okResult.Value ;
  //
  //IMPORTANT ONLY BELOW two lines  need.
  //

var actualConfigurationJStr=JsonConvert.SerializeObject( okResult.Value );
var hereObjectWithStrongType=JsonConvert.DeserializeObject<Configuration>(actualConfigurationJStr);

    
            // Now you can compare with expected values
            actualConfuguration.Should().BeEquivalentTo(expected);
        }
    }

Другие вопросы по теме