38

我试图在我的一个控制器中对返回 JsonResult 的方法进行单元测试。令我惊讶的是,以下代码不起作用:

[HttpPost]
public JsonResult Test() {
    return Json(new {Id = 123});
}

这就是我测试它的方式(另请注意,测试代码位于另一个程序集中):

// Act
dynamic jsonResult = testController.Test().Data;

// Assert
Assert.AreEqual(123, jsonResult.Id);

Assert抛出异常:

“object”不包含“Id”的定义

我已经通过使用以下方法解决了它:

[HttpPost]
public JsonResult Test() {
   dynamic data = new ExpandoObject();
   data.Id = 123;
   return Json(data);
}

我试图理解为什么第一个不起作用?它似乎也适用于基本上除了匿名类型之外的任何东西。

4

3 回答 3

37

需要明确的是,您遇到的具体问题是 C# 动态不适用于非公共成员。这是设计使然,大概是为了阻止这种事情。由于正如 LukLed 所说,匿名类型仅在同一程序集中是公共的(或者更准确地说,匿名类型只是标记为internal,而不是public),因此您遇到了这个障碍。

可能最干净的解决方案是供您使用InternalsVisibleTo。它允许您命名另一个可以访问其非公共成员的程序集。将其用于测试是其存在的主要原因之一。在您的示例中,您将在主项目的 AssemblyInfo.cs 中放置以下行:

[assembly: InternalsVisibleTo("AssemblyNameOfYourTestProject")]

一旦你这样做,错误就会消失(我只是自己尝试过)。

或者,您可以只使用蛮力反射:

Assert.AreEqual(123, jsonResult.GetType().GetProperty("Id").GetValue(jsonResult, null));
于 2013-06-01T20:48:48.583 回答
14

阅读了这里的回复,然后再看更远的地方,我发现了一篇2009 年的 msdn 博客文章,再次采用了不同的方法。但是.. 在评论中是Kieran的一个非常简单且非常优雅的解决方案......使用.ToString().

在你原来的情况下:

[HttpPost]
public JsonResult Test()
{
    return Json(new {Id = 123});
}

您可以通过以下方式进行测试:

var jsonResult = controller.Test();
Assert.AreEqual("{Id = 123}", jsonResult.Data.ToString());

我更喜欢这个解决方案,因为它:

  • 避免更改原始代码(InternalsVisibleTo, ExpandoObject),
  • 避免使用MvcContribRhinoMocks(这两个都没有问题,但为什么要添加只是为了能够测试JsonResult?),并且,
  • 避免使用反射(增加了测试的复杂性)。
于 2014-02-11T11:25:57.787 回答
5

匿名类型是内部的,因此您不能将它们暴露给另一个库,即带有测试的库。如果您将测试代码与控制器放在同一个库中,它将起作用。

于 2013-06-01T19:49:44.637 回答