Как unit test метод действия, возвращающий JsonResult?
Если у меня такой контроллер:
[HttpPost]
public JsonResult FindStuff(string query)
{
var results = _repo.GetStuff(query);
var jsonResult = results.Select(x => new
{
id = x.Id,
name = x.Foo,
type = x.Bar
}).ToList();
return Json(jsonResult);
}
В принципе, я извлекаю материал из своего репозитория, а затем проектирую его в List<T>
анонимных типов.
Как я могу его протестировать?
System.Web.Mvc.JsonResult
имеет свойство, называемое Data
, но оно типа object
, как мы и ожидали.
Так значит ли это, если я хочу проверить, что объект JSON имеет свойства, которые я ожидаю ( "id", "name", "type" ), мне нужно использовать отражение?
EDIT:
Здесь мой тест:
// Arrange.
const string autoCompleteQuery = "soho";
// Act.
var actionResult = _controller.FindLocations(autoCompleteQuery);
// Assert.
Assert.IsNotNull(actionResult, "No ActionResult returned from action method.");
dynamic jsonCollection = actionResult.Data;
foreach (dynamic json in jsonCollection)
{
Assert.IsNotNull(json.id,
"JSON record does not contain \"id\" required property.");
Assert.IsNotNull(json.name,
"JSON record does not contain \"name\" required property.");
Assert.IsNotNull(json.type,
"JSON record does not contain \"type\" required property.");
}
Но я получаю ошибку цикла в цикле, заявляя, что "объект не содержит определения для id".
Когда я останавливаю точку, actionResult.Data
определяется как List<T>
анонимных типов, поэтому я считаю, что если я перечислил их, я могу проверить свойства. Внутри цикла объект имеет свойство с именем "id" - поэтому не уверен, в чем проблема.
Ответы
Ответ 1
RPM, вы выглядите правильно. Мне еще многое предстоит узнать о dynamic
, и я не могу заставить Marc работать. Итак, вот как я это делал раньше. Вы можете найти это полезным. Я просто написал простой метод расширения:
public static object GetReflectedProperty(this object obj, string propertyName)
{
obj.ThrowIfNull("obj");
propertyName.ThrowIfNull("propertyName");
PropertyInfo property = obj.GetType().GetProperty(propertyName);
if (property == null)
{
return null;
}
return property.GetValue(obj, null);
}
Затем я просто использую это для выполнения утверждений по моим данным Json:
JsonResult result = controller.MyAction(...);
...
Assert.That(result.Data, Is.Not.Null, "There should be some data for the JsonResult");
Assert.That(result.Data.GetReflectedProperty("page"), Is.EqualTo(page));
Ответ 2
Я знаю, что я немного опаздываю на этих парней, но я узнал, почему динамическое решение не работает:
JsonResult
возвращает анонимный объект, и по умолчанию это internal
, поэтому они должны быть видимыми для проекта тестов.
Откройте проект приложения ASP.NET MVC и найдите AssemblyInfo.cs
в папке "Свойства". Откройте AssemblyInfo.cs и добавьте следующую строку в конец этого файла.
[assembly: InternalsVisibleTo("MyProject.Tests")]
Цитата из: http://weblogs.asp.net/gunnarpeipman/archive/2010/07/24/asp-net-mvc-using-dynamic-type-to-test-controller-actions-returning-jsonresult.aspx
Я подумал, что было бы неплохо иметь это для записи. Работает как шарм
Ответ 3
Я немного опоздал на вечеринку, но создал небольшую обертку, которая позволяет мне использовать свойства dynamic
. На этот ответ я работал над ASP.NET Core 1.0 RC2, но я верю, что если вы замените resultObject.Value
на resultObject.Data
, он должен работать для неосновных версий.
public class JsonResultDynamicWrapper : DynamicObject
{
private readonly object _resultObject;
public JsonResultDynamicWrapper([NotNull] JsonResult resultObject)
{
if (resultObject == null) throw new ArgumentNullException(nameof(resultObject));
_resultObject = resultObject.Value;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (string.IsNullOrEmpty(binder.Name))
{
result = null;
return false;
}
PropertyInfo property = _resultObject.GetType().GetProperty(binder.Name);
if (property == null)
{
result = null;
return false;
}
result = property.GetValue(_resultObject, null);
return true;
}
}
Использование, предполагая следующий контроллер:
public class FooController : Controller
{
public IActionResult Get()
{
return Json(new {Bar = "Bar", Baz = "Baz"});
}
}
Тест (xUnit):
// Arrange
var controller = new FoosController();
// Act
var result = await controller.Get();
// Assert
var resultObject = Assert.IsType<JsonResult>(result);
dynamic resultData = new JsonResultDynamicWrapper(resultObject);
Assert.Equal("Bar", resultData.Bar);
Assert.Equal("Baz", resultData.Baz);
Ответ 4
Я расширяю решение от Matt Greer и придумываю это небольшое расширение:
public static JsonResult IsJson(this ActionResult result)
{
Assert.IsInstanceOf<JsonResult>(result);
return (JsonResult) result;
}
public static JsonResult WithModel(this JsonResult result, object model)
{
var props = model.GetType().GetProperties();
foreach (var prop in props)
{
var mv = model.GetReflectedProperty(prop.Name);
var expected = result.Data.GetReflectedProperty(prop.Name);
Assert.AreEqual(expected, mv);
}
return result;
}
И я просто запускаю unittest как это:
- Задайте ожидаемый результат:
var expected = new
{
Success = false,
Message = "Name is required"
};
- утвердить результат:
// Assert
result.IsJson().WithModel(expected);
Ответ 5
Здесь я использую, возможно, он кому-то полезен. Он проверяет действие, которое возвращает объект JSON для использования в клиентской функциональности. Он использует Moq и FluentAssertions.
[TestMethod]
public void GetActivationcode_Should_Return_JSON_With_Filled_Model()
{
// Arrange...
ActivatiecodeController activatiecodeController = this.ActivatiecodeControllerFactory();
CodeModel model = new CodeModel { Activation = "XYZZY", Lifespan = 10000 };
this.deviceActivatieModelBuilder.Setup(x => x.GenereerNieuweActivatiecode()).Returns(model);
// Act...
var result = activatiecodeController.GetActivationcode() as JsonResult;
// Assert...
((CodeModel)result.Data).Activation.Should().Be("XYZZY");
((CodeModel)result.Data).Lifespan.Should().Be(10000);
}
Ответ 6
Мое решение состоит в том, чтобы написать метод расширения:
using System.Reflection;
using System.Web.Mvc;
namespace Tests.Extensions
{
public static class JsonExtensions
{
public static object GetPropertyValue(this JsonResult json, string propertyName)
{
return json.Data.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public).GetValue(json.Data, null);
}
}
}
Ответ 7
Если в тесте вы знаете, что именно должно быть результатом данных Json, тогда вы можете просто сделать что-то вроде этого:
result.Data.ToString().Should().Be(new { param = value}.ToString());
P.S. Это было бы, если бы вы использовали FluentAssertions.Mvc5 - но его не должно быть сложно преобразовать в любые инструменты тестирования, которые вы используете.