Skip to content

Commit

Permalink
Write member values of records
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Arnold committed Mar 11, 2023
1 parent 9b223cc commit 536b008
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Common/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ private static bool IsTuple(this Type type)
#endif
}

private static bool IsAnonymousType(this Type type)
public static bool IsAnonymousType(this Type type)
{
bool nameContainsAnonymousType = type.FullName.Contains("AnonymousType", StringComparison.Ordinal);

Expand Down
23 changes: 8 additions & 15 deletions Src/FluentAssertions/Formatting/DefaultValueFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting
return;
}

if (HasDefaultToStringImplementation(value) || IsAnonymousType(value))
if (HasCompilerGeneratedToStringImplementation(value))
{
WriteTypeAndMemberValues(value, formattedGraph, formatChild);
}
Expand Down Expand Up @@ -63,25 +63,18 @@ protected virtual MemberInfo[] GetMembers(Type type)
return type.GetNonPrivateMembers(MemberVisibility.Public).ToArray();
}

private static bool HasDefaultToStringImplementation(object value)
private static bool HasCompilerGeneratedToStringImplementation(object value)
{
string str = value.ToString();
Type type = value.GetType();

return str is null || str == value.GetType().ToString();
return HasDefaultToStringImplementation(value) || type.IsAnonymousType() || type.IsRecord();
}

private static bool IsAnonymousType(object value)
private static bool HasDefaultToStringImplementation(object value)
{
return value is not null && IsAnonymousType(value.GetType());
}
string str = value.ToString();

private static bool IsAnonymousType(Type type)
{
return type.Namespace is null
&& Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), inherit: false)
&& type.Name.Contains("AnonymousType", StringComparison.Ordinal)
&& (type.Name.StartsWith("<>", StringComparison.Ordinal)
|| type.Name.StartsWith("VB$", StringComparison.Ordinal));
return str is null || str == value.GetType().ToString();
}

private void WriteTypeAndMemberValues(object obj, FormattedObjectGraph formattedGraph, FormatChild formatChild)
Expand All @@ -93,7 +86,7 @@ private void WriteTypeAndMemberValues(object obj, FormattedObjectGraph formatted

private void WriteTypeName(FormattedObjectGraph formattedGraph, Type type)
{
if (!IsAnonymousType(type))
if (!type.IsAnonymousType())
{
formattedGraph.AddLine(TypeDisplayName(type));
}
Expand Down
39 changes: 34 additions & 5 deletions Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,28 +208,53 @@ public void When_the_object_is_an_anonymous_type_it_should_show_the_properties_r
// Arrange
var stuff = new
{
Description = "stuff",
Description = "absent",
SingleChild = new { ChildId = 4 },
Children = new[] { 10, 20, 30, 40 },
};

var expectedStuff = new
{
Id = 7,
Description = "stuff",
SingleChild = new { ChildId = 4 },
Children = new[] { 10, 20, 30, 40 },
};

// Act
Action act = () => stuff.Should()
.Be(expectedStuff);
Action act = () => stuff.Should().Be(expectedStuff);

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*ChildId =*")
.WithMessage("*Children = {10, 20, 30, 40}*");
}

[Fact]
public void When_the_object_is_a_record_it_should_show_the_properties_recursively()
{
// Arrange
var stuff = new StuffRecord(
RecordId: 9,
RecordDescription: "descriptive",
SingleChild: new(ChildRecordId: 80),
RecordChildren: new() { 4, 5, 6, 7 });

var expectedStuff = new
{
RecordDescription = "WRONG_DESCRIPTION",
};

// Act
Action act = () => stuff.Should().Be(expectedStuff);

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*RecordId =*")
.WithMessage("*RecordDescription =*")
.WithMessage("*SingleChild =*")
.WithMessage("*ChildRecordId = 80*")
.WithMessage("*RecordChildren = {4, 5, 6, 7}*");
}

[Fact]
public void When_the_to_string_override_throws_it_should_use_the_default_behavior()
{
Expand Down Expand Up @@ -715,6 +740,10 @@ public class Stuff<TChild> : BaseStuff
public List<TChild> Children { get; set; }
}

public record StuffRecord(int RecordId, string RecordDescription, ChildRecord SingleChild, List<int> RecordChildren);

public record ChildRecord(int ChildRecordId);

[Fact]
public void When_a_custom_formatter_exists_in_any_loaded_assembly_it_should_override_the_default_formatters()
{
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ sidebar:

### Fixes
* Improved robustness of several assertions when they're wrapped in an `AssertionScope` - [#2133](https://github.com/fluentassertions/fluentassertions/pull/2133)
* Format records and anonymous objects with their member values instead of the generated `ToString` - [#2144](https://github.com/fluentassertions/fluentassertions/pull/2144)

## 6.10.0

Expand Down

0 comments on commit 536b008

Please sign in to comment.