为什么 Assert.Equal 两个不同的 JObject(Newtonsoft.Json.Linq.JObject) 获得通过

zha*_*gr4 10 c# xunit json.net

我对 c#、xUnit 以及 Newtonsoft.Json 都很陌生。当我尝试在单元测试中使用 Assert.Equal() 方法比较两个不同的 JOject 时,它通过了,请参阅下面的示例代码

using Newtonsoft.Json.Linq;

namespace TestProject1
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            JObject jobj1 = JObject.FromObject(new { foo = "bar" });
            JObject jobj2 = JObject.FromObject(new { foo = 1 });
            JObject jobj3 = JObject.FromObject(new { foo = "b" });
            Assert.Equal(jobj1, jobj2); // output: pass
            Assert.Equal(jobj1, jobj3); // output: failure
            Assert.True(jobj1.Equals(jobj2)); // output: failure
            Assert.True(jobj1.Equals(jobj3)); // output: failure
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我不太明白这是如何发生的,我应该深入研究 xUnit 还是 Newtonsoft.Json?

我发现 Newtonsoft.Json 有一个DeepEquals 方法,但我不确定 xUnit 调用哪个比较器。

Gur*_*ron 11

这是 xUnit 断言 + Newtonsoft Json.NET 的一个错误。原因有两个:

  • 从Newtonsoft方面:
    • JToken(库中“所有内容”的基本类型)实现IEnumerable
    • JValue实现IComparable(并且IEnumerable因为它继承自JToken),当两个JValues 不兼容时会抛出异常
  • 从 xUnit 方面 - 如果类型实现IComparable并且比较器抛出错误,则忽略该错误,然后如果类型实现IEnumerable它被视为集合并且JValue是一个空集合,因此作为集合它们是相等的

最小重现如下所示:

[Fact]
public void Test2()
{
    var jvLeft = JToken.FromObject(1);
    var jvRight = JToken.FromObject("bar");

    // some "debug" checks
    Assert.True(jvLeft is JValue);
    Assert.Empty(jvLeft);
    Assert.Throws<FormatException>(() => (jvRight as IComparable).CompareTo(jvLeft));

    Assert.Equal(jvRight, jvLeft); // output: pass
}
Run Code Online (Sandbox Code Playgroud)

修复方法之一是使用Assert.StrictEqual(仅适用于JValues):

[Fact]
public void Test2()
{
    var jvLeft = JToken.FromObject(1);
    var jvRight = JToken.FromObject("bar");
    Assert.StrictEqual(jvRight, jvLeft); // output: fail
}
Run Code Online (Sandbox Code Playgroud)

或者使用/跟进JToken.DeepEquals(正如您所发现的):

[Fact]
public void Test12()
{
    JObject jobj1 = JObject.FromObject(new { foo = "bar" });
    JObject jobj2 = JObject.FromObject(new { foo = 1 });
    Assert.Equal(jobj1, jobj2); // output: pass
    Assert.True(JToken.DeepEquals(jobj1, jobj2)); // output: fail
}
Run Code Online (Sandbox Code Playgroud)

笔记: