I am quite new to c#, xUnit as well as Newtonsoft.Json. While I am trying compare two different JOject using Assert.Equal() method in unit test, it pass, refer to the below example code
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
}
}
}
I don't quite understand how this happens, should I look deep into xUnit or Newtonsoft.Json?
I find there is a DeepEquals method from Newtonsoft.Json, but I am not sure which comparator does xUnit call.
This is kind of a bug of xUnit asserts + Newtonsoft Json.NET. The reason is two-fold:
JToken
(base type for "everything" in the library) implements IEnumerable
JValue
implements IComparable
(and IEnumerable
because it inherits from JToken
) which throws when two JValue
s are incompatibleIComparable
and comparer throws error, the error is ignored, then if type implements IEnumerable
it is treated as a collection and JValue
is an empty collection, so as collections they are equalMinimal repro looks like:
[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
}
One of the fixes is to use Assert.StrictEqual
(works correctly for JValue
s only):
[Fact]
public void Test2()
{
var jvLeft = JToken.FromObject(1);
var jvRight = JToken.FromObject("bar");
Assert.StrictEqual(jvRight, jvLeft); // output: fail
}
Or use/follow up with the JToken.DeepEquals
(as you have discovered):
[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
}
Notes: