【问题标题】:How do you unit test ASP.NET Core MVC Controllers that return anonymous objects?如何对返回匿名对象的 ASP.NET Core MVC 控制器进行单元测试?
【发布时间】:2016-07-18 14:51:10
【问题描述】:

我在对返回匿名对象的 ASP.NET Core MVC 控制器进行单元测试时遇到问题。单元测试设置在单独的项目中,直接从主项目中调用控制器方法。

控制器方法返回 IActionResult,但通常这些是 OkObjectResultBadRequestObjectResult 对象,它们被转换为带有适当 HTTP 状态代码的 JSON 响应。匿名对象作为 ObjectResult 对象的构造函数参数传递,我正试图针对这些对象进行断言(可通过 ObjectResult.Value 访问)。

我发现这个问题 (how can i access internals in asp.net 5) 的答案是使用动态并添加

[assembly: InternalsVisibleTo("Namespace")]

到 AssemblyInfo.cs 以允许测试项目访问匿名对象的内部对象属性。但是,最新版本的 ASP.NET Core MVC 没有 AssemblyInfo.cs,并且按照链接问题的答案中的建议添加一个也不起作用。

现在是否有其他位置可以添加 InternalsVisibleTo 还是我遗漏了什么?

【问题讨论】:

标签: c# unit-testing asp.net-core-mvc anonymous-types


【解决方案1】:

来自this answer 的原创想法,采用更通用的方法。使用自定义 DynamicObject 作为包装器通过反射检查值,无需添加 InternalsVisibleTo

public class DynamicObjectResultValue : DynamicObject, IEquatable<DynamicObjectResultValue> {
    private readonly object value;

    public DynamicObjectResultValue(object value) {
        this.value = value;
    }

    #region Operators
    public static bool operator ==(DynamicObjectResultValue a, DynamicObjectResultValue b) {
        // If both are null, or both are same instance, return true.
        if (System.Object.ReferenceEquals(a, b)) {
            return true;
        }
        // If one is null, but not both, return false.
        if (ReferenceEquals((object)a, null) || ReferenceEquals((object)b, null)) {
            return false;
        }
        // Return true if the fields match:
        return a.value == b.value;
    }

    public static bool operator !=(DynamicObjectResultValue a, DynamicObjectResultValue b) {
        return !(a == b);
    }
    #endregion

    public override IEnumerable<string> GetDynamicMemberNames() {
        return value.GetType().GetProperties().Select(p => p.Name);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        //initialize value
        result = null;
        //Search possible matches and get its value
        var property = value.GetType().GetProperty(binder.Name);
        if (property != null) {
            // If the property is found, 
            // set the value parameter and return true. 
            var propertyValue = property.GetValue(value, null);
            result = propertyValue;
            return true;
        }
        // Otherwise, return false. 
        return false;
    }

    public override bool Equals(object obj) {
        if (obj is DynamicObjectResultValue)
            return Equals(obj as DynamicObjectResultValue);
        // If parameter is null return false.
        if (ReferenceEquals(obj, null)) return false;
        // Return true if the fields match:
        return this.value == obj;
    }

    public bool Equals(DynamicObjectResultValue other) {
        // If parameter is null return false.
        if (ReferenceEquals(other, null)) return false;
        // Return true if the fields match:
        return this.value == other.value;
    }

    public override int GetHashCode() {
        return ToString().GetHashCode();
    }

    public override string ToString() {
        return string.Format("{0}", value);
    }
}

假设以下控制器

public class FooController : Controller {

    public IActionResult GetAnonymousObject() {

        var jsonResult = new {
            id = 1,
            name = "Foo",
            type = "Bar"
        };

        return Ok(jsonResult);
    }

    public IActionResult GetAnonymousCollection() {

        var jsonResult = Enumerable.Range(1, 20).Select(x => new {
            id = x,
            name = "Foo" + x,
            type = "Bar" + x
        }).ToList();

        return Ok(jsonResult);
    }
}

测试可能看起来像

[TestMethod]
public void TestDynamicResults() {
    //Arrange
    var controller = new FooController();

    //Act
    var result = controller.GetAnonymousObject() as OkObjectResult;

    //Assert
    dynamic obj = new DynamicObjectResultValue(result.Value);

    Assert.IsNotNull(obj);
    Assert.AreEqual(1, obj.id);
    Assert.AreEqual("Foo", obj.name);
    Assert.AreEqual(3, obj.name.Length);
    Assert.AreEqual("Bar", obj.type);
}

[TestMethod]
public void TestDynamicCollection() {
    //Arrange
    var controller = new FooController();

    //Act
    var result = controller.GetAnonymousCollection() as OkObjectResult;

    //Assert
    Assert.IsNotNull(result, "No ActionResult returned from action method.");
    dynamic jsonCollection = result.Value;
    foreach (dynamic value in jsonCollection) {
        dynamic json = new DynamicObjectResultValue(value);

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

【讨论】:

  • 对于不再使用 AssemblyInfo 的 ASP.NET Core MVC 项目,这是一个非常优雅的解决方案。效果很好,所以我接受了它作为答案。
  • 这太棒了!给了我需要的 97% :) 现在,大约 3%... 是否有可能获得 jsonCollection 的长度?以及获取 jsonCollection[0] 需要什么,所以我不仅可以断言 NotNull,还可以断言实际值:Assert.Equal(4, jsonCollection.Count())Assert.Equal(25, jsonCollection[0].id)。如果您希望我开始一个新线程 - 很高兴这样做!
  • @Felix 添加线程并让我知道。自从把它放在这里以来,我一直在努力改进这项工作。当我有机会时,我会看看你评论中的细节。离开我的头顶。对于jsonCollection[0],我在想override TryGetIndex。因为您正在处理dynamicAssert.Equal(4, (int)jsonCollection.Count()),所以您必须进行断言。我一直在尝试像你一样做,但到目前为止还没有骰子。铸造似乎有效,所以我坚持了下来。
  • 完成。提前谢谢你:) stackoverflow.com/questions/38546914/…
  • @行话。为similar question 扩展了课程。看看这对你是否也有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-04
相关资源
最近更新 更多