Skip to content

Commit

Permalink
Merge pull request #30 from ronaldkroon/Improve_error_messages
Browse files Browse the repository at this point in the history
Improve error messages
  • Loading branch information
dennisdoomen committed Oct 17, 2019
2 parents 6958420 + 2d029d1 commit a270667
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 46 deletions.
60 changes: 52 additions & 8 deletions Src/FluentAssertions.Json/JTokenAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,20 @@ public JTokenAssertions(JToken subject)
public AndConstraint<JTokenAssertions> BeEquivalentTo(string expected, string because = "",
params object[] becauseArgs)
{
JToken parsedExpected = JToken.Parse(expected);
JToken parsedExpected;
try
{
parsedExpected = JToken.Parse(expected);
}
catch (Exception ex)
{
throw new ArgumentException(
$"Unable to parse expected JSON string:{Environment.NewLine}" +
$"{expected}{Environment.NewLine}" +
"Check inner exception for more details.",
nameof(expected), ex);
}

return BeEquivalentTo(parsedExpected, because, becauseArgs);
}

Expand All @@ -75,9 +88,12 @@ public JTokenAssertions(JToken subject)
{
Difference difference = JTokenDifferentiator.FindFirstDifference(Subject, expected);

var message = $"Expected JSON document {Format(Subject, true).Replace("{", "{{").Replace("}", "}}")}" +
$" to be equivalent to {Format(expected, true).Replace("{", "{{").Replace("}", "}}")}" +
$"{{reason}}, but {difference}.";
var message = $"JSON document {difference}.{Environment.NewLine}" +
$"Expected{Environment.NewLine}" +
$"{Format(Subject, true).Replace("{", "{{").Replace("}", "}}")}{Environment.NewLine}" +
$"to be equivalent to{Environment.NewLine}" +
$"{Format(expected, true).Replace("{", "{{").Replace("}", "}}")}{Environment.NewLine}" +
"{reason}.";

Execute.Assertion
.ForCondition(difference == null)
Expand All @@ -99,10 +115,24 @@ public JTokenAssertions(JToken subject)
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see paramref="because" />.
/// </param>
public AndConstraint<JTokenAssertions> NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs)
public AndConstraint<JTokenAssertions> NotBeEquivalentTo(string unexpected, string because = "",
params object[] becauseArgs)
{
JToken parsedExpected = JToken.Parse(unexpected);
return NotBeEquivalentTo(parsedExpected, because, becauseArgs);
JToken parsedUnexpected;
try
{
parsedUnexpected = JToken.Parse(unexpected);
}
catch (Exception ex)
{
throw new ArgumentException(
$"Unable to parse unexpected JSON string:{Environment.NewLine}" +
$"{unexpected}{Environment.NewLine}" +
"Check inner exception for more details.",
nameof(unexpected), ex);
}

return NotBeEquivalentTo(parsedUnexpected, because, becauseArgs);
}

/// <summary>
Expand Down Expand Up @@ -408,7 +438,21 @@ public AndConstraint<JTokenAssertions> ContainSubtree(JToken subtree, string bec
/// </code>
public AndConstraint<JTokenAssertions> ContainSubtree(string subtree, string because = "", params object[] becauseArgs)
{
return ContainSubtree(JToken.Parse(subtree), because, becauseArgs);
JToken subtreeToken;
try
{
subtreeToken = JToken.Parse(subtree);
}
catch (Exception ex)
{
throw new ArgumentException(
$"Unable to parse expected JSON string:{Environment.NewLine}" +
$"{subtree}{Environment.NewLine}" +
"Check inner exception for more details.",
nameof(subtree), ex);
}

return ContainSubtree(subtreeToken, because, becauseArgs);
}

private bool JTokenContainsSubtree(JToken token, JToken subtree)
Expand Down
16 changes: 8 additions & 8 deletions Src/FluentAssertions.Json/JTokenDifferentiator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,21 @@ public override string ToString()
switch (Kind)
{
case DifferenceKind.ActualIsNull:
return "it is null";
return "is null";
case DifferenceKind.ExpectedIsNull:
return "it is not null";
return "is not null";
case DifferenceKind.OtherType:
return $"the type is different at {Path}";
return $"has a different type at {Path}";
case DifferenceKind.OtherName:
return $"the name is different at {Path}";
return $"has a different name at {Path}";
case DifferenceKind.OtherValue:
return $"the value is different at {Path}";
return $"has a different value at {Path}";
case DifferenceKind.DifferentLength:
return $"the length is different at {Path}";
return $"has a different length at {Path}";
case DifferenceKind.ActualMissesProperty:
return $"it misses property {Path}";
return $"misses property {Path}";
case DifferenceKind.ExpectedMissesProperty:
return $"it has extra property {Path}";
return $"has extra property {Path}";
default:
throw new ArgumentOutOfRangeException();
}
Expand Down
122 changes: 92 additions & 30 deletions Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using FluentAssertions.Formatting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
using Xunit.Sdk;
Expand Down Expand Up @@ -75,67 +76,67 @@ public void When_objects_differ_BeEquivalentTo_should_fail()
Tuple.Create(
(string)null,
"{ id: 2 }",
"it is null")
"is null")
,
Tuple.Create(
"{ id: 1 }",
(string)null,
"it is not null")
"is not null")
,
Tuple.Create(
"{ items: [] }",
"{ items: 2 }",
"the type is different at $.items")
"has a different type at $.items")
,
Tuple.Create(
"{ items: [ \"fork\", \"knife\" , \"spoon\" ]}",
"{ items: [ \"fork\", \"knife\" ]}",
"the length is different at $.items")
"has a different length at $.items")
,
Tuple.Create(
"{ items: [ \"fork\", \"knife\" , \"spoon\" ]}",
"{ items: [ \"fork\", \"knife\" ]}",
"the length is different at $.items")
"{ items: [ \"fork\", \"knife\" , \"spoon\" ]}",
"has a different length at $.items")
,
Tuple.Create(
"{ items: [ \"fork\", \"knife\" , \"spoon\" ]}",
"{ items: [ \"fork\", \"spoon\", \"knife\" ]}",
"the value is different at $.items[1]")
"has a different value at $.items[1]")
,
Tuple.Create(
"{ tree: { } }",
"{ tree: \"oak\" }",
"the type is different at $.tree")
"has a different type at $.tree")
,
Tuple.Create(
"{ tree: { leaves: 10} }",
"{ tree: { branches: 5, leaves: 10 } }",
"it misses property $.tree.branches")
"misses property $.tree.branches")
,
Tuple.Create(
"{ tree: { branches: 5, leaves: 10 } }",
"{ tree: { leaves: 10} }",
"it has extra property $.tree.branches")
"has extra property $.tree.branches")
,
Tuple.Create(
"{ tree: { leaves: 5 } }",
"{ tree: { leaves: 10} }",
"the value is different at $.tree.leaves")
"has a different value at $.tree.leaves")
,
Tuple.Create(
"{ eyes: \"blue\" }",
"{ eyes: [] }",
"the type is different at $.eyes")
"has a different type at $.eyes")
,
Tuple.Create(
"{ eyes: \"blue\" }",
"{ eyes: 2 }",
"the type is different at $.eyes")
"has a different type at $.eyes")
,
Tuple.Create(
"{ id: 1 }",
"{ id: 2 }",
"the value is different at $.id")
"has a different value at $.id")
};

foreach (var testCase in testCases)
Expand All @@ -148,9 +149,11 @@ public void When_objects_differ_BeEquivalentTo_should_fail()
var expected = (expectedJson != null) ? JToken.Parse(expectedJson) : null;

var expectedMessage =
$"Expected JSON document {Format(actual, true)} " +
$"to be equivalent to {Format(expected, true)}, " +
"but " + expectedDifference + ".";
$"JSON document {expectedDifference}." +
$"Expected" +
$"{Format(actual, true)}" +
$"to be equivalent to" +
$"{Format(expected, true)}.";

//-----------------------------------------------------------------------------------------------------------
// Act & Assert
Expand All @@ -172,29 +175,32 @@ public void When_properties_differ_BeEquivalentTo_should_fail()
Tuple.Create<JToken, JToken, string>(
new JProperty("eyes", "blue"),
new JArray(),
"the type is different at $")
"has a different type at $")
,
Tuple.Create<JToken, JToken, string>(
new JProperty("eyes", "blue"),
new JProperty("hair", "black"),
"the name is different at $")
"has a different name at $")
,
};

foreach (var testCase in testCases)
{
var a = testCase.Item1;
var b = testCase.Item2;

var actual = testCase.Item1;
var expected = testCase.Item2;
var expectedDifference = testCase.Item3;

var expectedMessage =
$"Expected JSON document {Format(a, true)} " +
$"to be equivalent to {Format(b, true)}, " +
"but " + testCase.Item3 + ".";
$"JSON document {expectedDifference}." +
$"Expected" +
$"{Format(actual, true)}" +
$"to be equivalent to" +
$"{Format(expected, true)}.";

//-----------------------------------------------------------------------------------------------------------
// Act & Assert
//-----------------------------------------------------------------------------------------------------------
a.Should().Invoking(x => x.BeEquivalentTo(b))
actual.Should().Invoking(x => x.BeEquivalentTo(expected))
.Should().Throw<XunitException>()
.WithMessage(expectedMessage);
}
Expand Down Expand Up @@ -312,6 +318,42 @@ public void When_checking_whether_a_JToken_is_equivalent_to_the_string_represent
//-----------------------------------------------------------------------------------------------------------
actualJSON.Should().BeEquivalentTo(jsonString);
}

[Fact]
public void When_checking_equivalency_with_an_invalid_expected_string_it_should_provide_a_clear_error_message()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var actualJson = JToken.Parse("{ \"id\": null }");
var expectedString = "{ invalid JSON }";

//-----------------------------------------------------------------------------------------------------------
// Act & Assert
//-----------------------------------------------------------------------------------------------------------
actualJson.Should().Invoking(x => x.BeEquivalentTo(expectedString))
.Should().Throw<ArgumentException>()
.WithMessage($"Unable to parse expected JSON string:{expectedString}*")
.WithInnerException<JsonReaderException>();
}

[Fact]
public void When_checking_non_equivalency_with_an_invalid_unexpected_string_it_should_provide_a_clear_error_message()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var actualJson = JToken.Parse("{ \"id\": null }");
var unexpectedString = "{ invalid JSON }";

//-----------------------------------------------------------------------------------------------------------
// Act & Assert
//-----------------------------------------------------------------------------------------------------------
actualJson.Should().Invoking(x => x.NotBeEquivalentTo(unexpectedString))
.Should().Throw<ArgumentException>()
.WithMessage($"Unable to parse unexpected JSON string:{unexpectedString}*")
.WithInnerException<JsonReaderException>();
}

[Fact]
public void When_specifying_a_reason_why_object_should_be_equivalent_it_should_use_that_in_the_error_message()
Expand All @@ -323,10 +365,12 @@ public void When_specifying_a_reason_why_object_should_be_equivalent_it_should_u
var expected = JToken.Parse("{ child: { expected: 'bar' } }");

var expectedMessage =
$"Expected JSON document {Format(subject, true)} " +
$"to be equivalent to {Format(expected, true)} " +
"because we want to test the failure message, " +
"but it misses property $.child.expected.";
$"JSON document misses property $.child.expected." +
$"Expected" +
$"{Format(subject, true)}" +
$"to be equivalent to" +
$"{Format(expected, true)} " +
"because we want to test the failure message.";

//-----------------------------------------------------------------------------------------------------------
// Act & Assert
Expand Down Expand Up @@ -1021,6 +1065,24 @@ public void When_property_types_dont_match_ContainSubtree_should_fail()
act.Should().Throw<XunitException>();
}

[Fact]
public void When_checking_subtree_with_an_invalid_expected_string_it_should_provide_a_clear_error_message()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var actualJson = JToken.Parse("{ \"id\": null }");
var invalidSubtree = "{ invalid JSON }";

//-----------------------------------------------------------------------------------------------------------
// Act & Assert
//-----------------------------------------------------------------------------------------------------------
actualJson.Should().Invoking(x => x.ContainSubtree(invalidSubtree))
.Should().Throw<ArgumentException>()
.WithMessage($"Unable to parse expected JSON string:{invalidSubtree}*")
.WithInnerException<JsonReaderException>();
}

#endregion

private static string Format(JToken value, bool useLineBreaks = false)
Expand Down

0 comments on commit a270667

Please sign in to comment.