Skip to content

Commit

Permalink
Write member values of tuples
Browse files Browse the repository at this point in the history
  • Loading branch information
benagain committed Mar 12, 2023
1 parent 5705223 commit 5829a5f
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 24 deletions.
11 changes: 10 additions & 1 deletion Src/FluentAssertions/Common/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal static class TypeExtensions

private static readonly ConcurrentDictionary<Type, bool> HasValueSemanticsCache = new();
private static readonly ConcurrentDictionary<Type, bool> TypeIsRecordCache = new();
private static readonly ConcurrentDictionary<Type, bool> TypeIsCompilerGeneratedCache = new();

private static readonly ConcurrentDictionary<(Type Type, MemberVisibility Visibility), IEnumerable<PropertyInfo>>
NonPrivatePropertiesCache = new();
Expand Down Expand Up @@ -556,7 +557,15 @@ public static bool HasValueSemantics(this Type type)
!IsKeyValuePair(t));
}

private static bool IsTuple(this Type type)
public static bool IsCompilerGeneratedType(this Type type)
{
return TypeIsCompilerGeneratedCache.GetOrAdd(type, static t =>
t.IsRecord() ||
t.IsAnonymousType() ||
t.IsTuple());
}

public static bool IsTuple(this Type type)
{
if (!type.IsGenericType)
{
Expand Down
6 changes: 3 additions & 3 deletions Src/FluentAssertions/Formatting/DefaultValueFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -67,7 +67,7 @@ private static bool HasCompilerGeneratedToStringImplementation(object value)
{
Type type = value.GetType();

return HasDefaultToStringImplementation(value) || type.IsAnonymousType() || type.IsRecord();
return HasDefaultToStringImplementation(value) || type.IsCompilerGeneratedType();
}

private static bool HasDefaultToStringImplementation(object value)
Expand All @@ -86,7 +86,7 @@ private void WriteTypeAndMemberValues(object obj, FormattedObjectGraph formatted

private void WriteTypeName(FormattedObjectGraph formattedGraph, Type type)
{
if (!type.IsAnonymousType())
if (!type.IsAnonymousType() && !type.IsTuple())
{
formattedGraph.AddLine(TypeDisplayName(type));
}
Expand Down
18 changes: 0 additions & 18 deletions Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,24 +216,6 @@ public void When_the_expectation_has_fewer_dimensions_than_a_multi_dimensional_s
"adding a `params object[]` overload cannot distinguish 'an array of objects' from 'an element which is an array of objects'");
}

[Fact]
public void When_the_expectation_is_an_anonymous_type_it_should_report_on_member_values()
{
// Arrange
var actual = new[] { new { MyCollection = new[] { "alpha" } } };
var expected = new { MyCollection = new List<string> { { "beta" } } };

// Act
Action act = () => actual.Should().ContainEquivalentOf(expected);

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected actual {*MyCollection = {\"alpha\"}*}*contain equivalent of*",
"anonymous types are recursively formatted")
.And.Message.Should().NotContain("AnonymousType",
"anonymous type information is compiler generated noise");
}

[Fact]
public void When_the_expectation_is_an_array_of_anonymous_types_it_should_respect_runtime_types()
{
Expand Down
23 changes: 21 additions & 2 deletions Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
Expand Down Expand Up @@ -225,7 +225,26 @@ public void When_the_object_is_an_anonymous_type_it_should_show_the_properties_r
// Assert
act.Should().Throw<XunitException>()
.WithMessage("*ChildId =*")
.WithMessage("*Children = {10, 20, 30, 40}*");
.WithMessage("*Children = {10, 20, 30, 40}*")
.And.Message.Should().NotContain("AnonymousType");
}

[Fact]
public void When_the_object_is_a_tuple_it_should_show_the_properties_recursively()
{
// Arrange
(int TupleId, string Description, List<int> Children) stuff = (1, "description", new() { 1, 2, 3, 4 });

(int, string, List<int>) expectedStuff = (2, "WRONG_DESCRIPTION", new List<int> { 4, 5, 6, 7 });

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

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*1*description*{1, 2, 3, 4}*")
.WithMessage("*2*WRONG_DESCRIPTION*{4, 5, 6, 7}*")
.And.Message.Should().NotContain("System.ValueTuple");
}

[Fact]
Expand Down
34 changes: 34 additions & 0 deletions Tests/FluentAssertions.Specs/Types/TypeExtensionsSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,40 @@ public void IsRecord_should_detect_records_correctly(Type type, bool expected)
type.IsRecord().Should().Be(expected);
}

[Theory]
[InlineData(typeof(Tuple<int>), true)]
[InlineData(typeof(Tuple<int, string>), true)]
[InlineData(typeof(Tuple<int, string, long>), true)]
[InlineData(typeof(Tuple<int, string, long, char>), true)]
[InlineData(typeof(Tuple<int, string, long, char, decimal>), true)]
[InlineData(typeof(Tuple<int, string, long, char, decimal, float>), true)]
[InlineData(typeof(Tuple<int, string, long, char, decimal, float, byte>), true)]
[InlineData(typeof(ValueTuple<int>), true)]
[InlineData(typeof(ValueTuple<int, string>), true)]
[InlineData(typeof(ValueTuple<int, string, long>), true)]
[InlineData(typeof(ValueTuple<int, string, long, char>), true)]
[InlineData(typeof(ValueTuple<int, string, long, char, decimal>), true)]
[InlineData(typeof(ValueTuple<int, string, long, char, decimal, float>), true)]
[InlineData(typeof(MyRecord), true)]
[InlineData(typeof(MyRecordStruct), true)]
[InlineData(typeof(MyRecordStructWithCustomPrintMembers), true)]
[InlineData(typeof(MyRecordStructWithOverriddenEquality), true)]
[InlineData(typeof(MyReadonlyRecordStruct), true)]
[InlineData(typeof(int), false)]
[InlineData(typeof(string), false)]
[InlineData(typeof(MyClass), false)]
public void IsCompilerGeneratedType_should_detect_records_and_tuples_correctly(Type type, bool expected)
{
type.IsCompilerGeneratedType().Should().Be(expected);
}

[Fact]
public void IsCompilerGeneratedType_should_detect_anonymous_types_correctly()
{
var value = new { Id = 1 };
value.GetType().IsCompilerGeneratedType().Should().BeTrue();
}

[Fact]
public void When_checking_if_anonymous_type_is_record_it_should_return_false()
{
Expand Down

0 comments on commit 5829a5f

Please sign in to comment.