如何对返回 JsonResult 的 Action 方法进行单元测试?

发布于 2024-10-17 12:53:41 字数 1438 浏览 14 评论 0原文

如果我有一个像这样的控制器:

[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 中。

我如何对其进行单元测试?

System.Web.Mvc.JsonResult 有一个名为 Data 的属性,但正如我们所期望的,它的类型为 object

那么这是否意味着如果我想测试 JSON 对象是否具有我期望的属性(“id”、“name”、“type”),我必须使用反射?

编辑:

这是我的测试:

// 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,因此我认为如果枚举这些,我就可以检查属性。在循环内部,对象确实有一个名为“id”的属性 - 所以不确定问题是什么。

If I have a controller like this:

[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);
}

Basically, I grab stuff from my repository, then project it into a List<T> of anonymous types.

How can I unit-test it?

System.Web.Mvc.JsonResult has a property called Data, but it's of type object, as we expected.

So does that mean if I want to test that the JSON object has the properties I expect ("id", "name", "type"), I have to use reflection?

EDIT:

Here's my test:

// 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.");
}

But I get a runtime error in the loop, stating "object does not contain a definition for id".

When I breakpoint, actionResult.Data is defined as a List<T> of anonymous types, so I figure if I enumerate through these, I can check the properties. Inside the loop, the object does have a property called "id" - so not sure what the issue is.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

夜深人未静 2024-10-24 12:53:41

我知道我对这些人有点晚了,但我发现了为什么动态解决方案不起作用:

JsonResult 返回一个匿名对象,默认情况下,这些是内部 code>,因此需要使它们对测试项目可见。

打开 ASP.NET MVC 应用程序项目并从名为 Properties 的文件夹中找到 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

我认为将这个记录下来会很好。就像魅力一样工作

I know I'm a bit late on this guys, but I found out why the dynamic solution wasn't working:

JsonResult returns an anonymous object and these are, by default, internal, so they need to be made visible to the tests project.

Open your ASP.NET MVC application project and find AssemblyInfo.cs from folder called Properties. Open AssemblyInfo.cs and add the following line to the end of this file.

[assembly: InternalsVisibleTo("MyProject.Tests")]

Quoted from: http://weblogs.asp.net/gunnarpeipman/archive/2010/07/24/asp-net-mvc-using-dynamic-type-to-test-controller-actions-returning-jsonresult.aspx

I thought it would be nice to have this one for the record. Works like a charm

飘落散花 2024-10-24 12:53:41

RPM,你看起来是正确的。关于动态,我还有很多东西需要学习,而且我也无法让 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));

RPM, you look to be correct. I still have much to learn about dynamic and I cannot get Marc's approach to work either. So here is how I was doing it before. You may find it helpful. I just wrote a simple extension method:

    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);
    }

Then I just use that to do assertions on my Json data:

        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));
小帐篷 2024-10-24 12:53:41

我来晚了一点,但我创建了一个小包装器,让我可以使用动态属性。截至此答案,我已经在 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);

I'm a bit late to the party, but I created a little wrapper that lets me then use dynamic properties. As of this answer I've got this working on ASP.NET Core 1.0 RC2, but I believe if you replace resultObject.Value with resultObject.Data it should work for non-core versions.

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;
    }
}

Usage, assuming the following controller:

public class FooController : Controller
{
    public IActionResult Get()
    {
        return Json(new {Bar = "Bar", Baz = "Baz"});
    }
}

The test (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);
影子的影子 2024-10-24 12:53:41

这是我使用的一个,也许对任何人都有用。它测试返回 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);
}

Here's one I use, perhaps it is of use to anyone. It tests an action that returns a JSON object for use in clientside functionality. It uses Moq and 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);
}
反目相谮 2024-10-24 12:53:41

我的解决方案是编写扩展方法:

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);
        }
    }
}

My solution is to write the extension method:

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);
        }
    }
}
金橙橙 2024-10-24 12:53:41

我扩展了 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;
    }

我只是运行单元测试,如下所示:
- 设置预期数据结果:

        var expected = new
        {
            Success = false,
            Message = "Name is required"
        };

- 断言结果:

        // Assert
        result.IsJson().WithModel(expected);

I extend the solution from Matt Greer and come up with this small extension:

    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;
    }

And i just run the unittest as this:
- Set the expected data result:

        var expected = new
        {
            Success = false,
            Message = "Name is required"
        };

- Assert the result:

        // Assert
        result.IsJson().WithModel(expected);
哀由 2024-10-24 12:53:41

如果在测试中你知道 Json 数据结果到底应该是什么,那么你可以这样做:

result.Data.ToString().Should().Be(new { param = value}.ToString());

PS 这将是如果你使用了 FluentAssertions.Mvc5 - 但将其转换为你想要的任何测试工具应该不难使用。

If in the test you know what exactly the Json data result should be then you can just do something like this:

result.Data.ToString().Should().Be(new { param = value}.ToString());

P.S. This would be if you had used FluentAssertions.Mvc5 - but it shouldn't be hard to convert it to whatever testing tools you use.

巴黎夜雨 2024-10-24 12:53:41

这就是我断言的方式

foreach (var item in jsonResult.Data as dynamic) {
    ((int)item.Id).ShouldBe( expected Id value );
    ((string)item.name).ShouldBe( "expected name value" );
}

This is how I assert it

foreach (var item in jsonResult.Data as dynamic) {
    ((int)item.Id).ShouldBe( expected Id value );
    ((string)item.name).ShouldBe( "expected name value" );
}
女皇必胜 2024-10-24 12:53:41

您可以将 JsonResult 的 value 属性转换为已知类型的实例,然后只需访问它的属性即可。

public static class JsonResultExtensions
{
    public static T ExtractType<T>(this JsonResult result)
    {
        var resultAsJson = JsonSerializer.Serialize(result.Value);
        return JsonSerializer.Deserialize<T>(resultAsJson);
    }
}

下面是扩展方法的使用示例:

MyModel model = jsonResult.ExtractType<MyModel>();
Assert.True(model.Success);

You can convert the value property of the JsonResult into an instance of a known type and then simply access it's properties.

public static class JsonResultExtensions
{
    public static T ExtractType<T>(this JsonResult result)
    {
        var resultAsJson = JsonSerializer.Serialize(result.Value);
        return JsonSerializer.Deserialize<T>(resultAsJson);
    }
}

below is an example of the use of the extension method:

MyModel model = jsonResult.ExtractType<MyModel>();
Assert.True(model.Success);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文