【问题标题】:How to unit test an Action method which returns JsonResult?如何对返回 JsonResult 的 Action 方法进行单元测试?
【发布时间】:2011-06-26 17:51:50
【问题描述】:

如果我有这样的控制器:

[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”),我必须使用反射?

编辑:

这是我的测试:

// 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”的属性 - 所以不确定是什么问题。

【问题讨论】:

  • 重新编辑 - 你可以尝试类似 var items = (IEnumerable)actionResult.Data; foreach(项目中的动态 obj){...}
  • 我在这里用`var list = (IList)data; Assert.AreEqual(list.Count, 2);动态 obj = 数据[0];断言.AreEqual(obj.id, 12); Assert.AreEqual(obj.name, "Fred"); Assert.AreEqual(obj.type, 'a');对象=数据[1];断言.AreEqual(obj.id,14); Assert.AreEqual(obj.name, "Jim"); Assert.AreEqual(obj.type, 'c'); foreach (动态 d in list) { if (d.id == null) throw new InvalidOperationException(); }` 看起来还不错……
  • 明天我到办公室时让我试试那个代码。干杯。
  • @RPM1984 在提取到 obj 时,objdynamic - 所以后期绑定 per item 仍然会发生。
  • @RPM - 没问题;我很高兴你有一个可行的解决方案

标签: c# asp.net-mvc json unit-testing jsonresult


【解决方案1】:

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

JsonResult 返回一个匿名对象,默认情况下它们是internal,因此需要将它们设置为对测试项目可见。

打开您的 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

我认为有这个记录会很好。像魅力一样工作

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 很高兴我不是唯一一个试图让 Marc 的解决方案发挥作用的人。这很好用。我将删除我的答案并接受这一点,因为这绝对是更好的方法。谢谢!
    • 作为记录,动态解决方案将起作用。它基本上与 this 相同,只是语法更漂亮。在内部它仍然会使用反射。就像 ViewBag 只是具有更漂亮语法的 ViewData 一样。我只是还没有研究足够的动态来实现它。当我有一点停机时间时,我会这样做。我知道它涉及实现IDynamicObject 并用它包装 JsonResult.Data。
    • ThrowIfNull 方法从何而来?您是否正在扩展课程或我错过了什么?
    • 这只是我的一个小扩展方法。如果它为 null 或返回对象,它会抛出 ArgumentNullException
    【解决方案3】:

    我参加聚会有点晚了,但我创建了一个小包装器,然后我可以使用dynamic 属性。截至这个答案,我已经在 ASP.NET Core 1.0 RC2 上运行了这个,但我相信如果你用 resultObject.Data 替换 resultObject.Value 它应该适用于非核心版本。

    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】:

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

      【讨论】:

      • 将结果转换为 JsonResult 最终帮助我使用 aspnetcore.mvc v2.0.1 访问 Value 属性。
      【解决方案5】:

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

      【讨论】:

        【解决方案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());
          

          附:如果您使用了 FluentAssertions.Mvc5,那就是这样 - 但将其转换为您使用的任何测试工具应该不难。

          【讨论】:

            【解决方案8】:

            我就是这样断言的

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-06-08
              • 2014-05-10
              • 2016-10-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多