Я создал 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);
Надеюсь, в вашем контроллере не так много кода, который действительно нуждается в тестировании, и большая его часть находится в других, более тестируемых классах! Обратите внимание, что, поскольку вы используете ASP.NET MVC Core 2.1, вы можете вместо этого начать использовать тип ActionResult<T>
. Это должно помочь в вашем тестировании.
Вам нужно дождаться вызова 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 можно увидеть во время выполнения, но я не могу его запрограммировать, поскольку он не существует.
А, тогда вам, наверное, просто нужно вернуть configuration
вместо Ok(configuration)
.
Да, думаю, я мог бы это сделать, но я искал способ получить ActionResult вместе с конфигурацией / конфигурациями, соответствующими запросам.
Почему это не документировано лучше? На получение возвращенного значения у меня ушло несколько часов. Спасибо.
@DavidG Большое спасибо за ваш комментарий, я застрял на этой глупой ошибке на час!
Вы можете получить протестированный контроллер без изменения возвращаемого типа.
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);
}
}
Для целей тестирования это кажется лучшим ответом
делает это var actualConfiguration = okResult.Value as Configuration; получить присвоить значения фактической конфигурации ??
Поскольку моя репутация не позволяет мне комментировать ответ @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?
Если вам нужно быстрое решение, используйте 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);
}
}
Вам следует дождаться асинхронного метода.