2011-02-14 150 views
39

如果我有一个这样的控制器:如何对返回JsonResult的Action方法进行单元测试?

[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”的属性 - 所以不知道问题是什么。

+0

重新编辑 - 你可以尝试像var items =(IEnumerable)actionResult.Data; foreach(动态obj中的项目){...} – 2011-02-14 22:57:05

+1

我已经在这里测试了' var list =(IList)数据; Assert.AreEqual(list.Count,2); dynamic obj = data [0]; Assert.AreEqual(obj.id,12); Assert.AreEqual(obj.name,“Fred”); Assert.AreEqual(obj.type,'a'); obj = data [1]; Assert.AreEqual(obj.id,14); Assert.AreEqual(obj.name,“Jim”); Assert.AreEqual(obj.type,'c'); foreach(动态列表中的d) if(d.id == null)throw new InvalidOperationException(); }`看起来很好...... – 2011-02-15 06:11:16

回答

14

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

我知道我是一个有点晚于这个家伙,但我发现为什么动态解决方案不起作用:

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

我认为这将是不错的这个备案。工程就像一个魅力

1

我的解决方案,从马特·格里尔延伸,并拿出这个小扩展:

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

- 断言结果:

​​
1

这里有一个I U se,也许这对任何人都有用。它测试返回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); 
} 
1

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

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

我有点迟到了,但我创建了一个小包装,让我,然后用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); 
0

如果在测试你确切地知道JSON数据结果应该是什么,那么你可以这样做:

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

P.S.这将是如果你使用FluentAssertions.Mvc5 - 但它不应该很难转换为你使用的任何测试工具。

相关问题