Skip to content

Conversation

@benagain
Copy link
Contributor

@benagain benagain commented Mar 11, 2023

#2140 - Enhance the output formatter so that record, anonymous types and tuples output are displayed as their member values rather than their ToString implementation.

Before:

Expected object to be <null> because classes without ToString output members, but found FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
{
    Children = {1, 2, 3, 4}, 
    Description = <null>, 
    StuffId = 1
}.
Expected object to be <null> because anonymous classes output members, but found { AnonymousClassId = 1, Children = System.Int32[] }.
Expected object to be <null> because records output members, but found StuffRecord { RecordId = 1, RecordDescription = description, RecordChildren = System.Collections.Generic.List`1[System.Int32] }.
Expected tuple to be <null> because tuples output members, but found (1, description, System.Collections.Generic.List`1[System.Int32]).

After:

Expected object to be <null> because classes without ToString output members, but found FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
{
    Children = {1, 2, 3, 4}, 
    Description = <null>, 
    StuffId = 1
}.
Expected object to be <null> because anonymous classes output members, but found {
    AnonymousClassId = 1, 
    Children = {1, 2, 3, 4}
}.
Expected object to be <null> because records output members, but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord
{
    RecordChildren = {1, 2, 3, 4}, 
    RecordDescription = "description", 
    RecordId = 1, 
    SingleChild = 
    FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord
    {
        ChildRecordId = 80
    }
}.
Expected tuple to be <null> because tuples output members, but found {
    Item1 = 1, 
    Item2 = "description", 
    Item3 = {1, 2, 3, 4}
}.

IMPORTANT

  • If the PR touches the public API, the changes have been approved in a separate issue with the "api-approved" label.
  • The code complies with the Coding Guidelines for C#.
  • The changes are covered by unit tests which follow the Arrange-Act-Assert syntax and the naming conventions such as is used in these tests.
  • If the PR adds a feature or fixes a bug, please update the release notes with a functional description that explains what the change means to consumers of this library, which are published on the website.
  • If the PR changes the public API the changes needs to be included by running AcceptApiChanges.ps1 or AcceptApiChanges.sh.
  • If the PR affects the documentation, please include your changes in this pull request so the documentation will appear on the website.
    • Please also run ./build.sh --target spellcheck or .\build.ps1 --target spellcheck before pushing and check the good outcome

@benagain benagain force-pushed the anonymous-types-formatting branch 2 times, most recently from 0bb526f to 536b008 Compare March 11, 2023 08:13
@coveralls
Copy link

coveralls commented Mar 11, 2023

Pull Request Test Coverage Report for Build 4643984026

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 45 of 45 (100.0%) changed or added relevant lines in 6 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.01%) to 97.2%

Totals Coverage Status
Change from base Build 4629906477: 0.01%
Covered Lines: 12881
Relevant Lines: 13124

💛 - Coveralls

Copy link
Member

@dennisdoomen dennisdoomen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Would be nice to have an example of the before and after situations in the PR description

@benagain benagain force-pushed the anonymous-types-formatting branch 2 times, most recently from b52e40f to 5829a5f Compare March 12, 2023 10:09
@dennisdoomen dennisdoomen requested a review from jnyrup March 12, 2023 10:56
@benagain
Copy link
Contributor Author

Hi @dennisdoomen @lg2de, is there anything else to do for this PR?

@dennisdoomen
Copy link
Member

Hi @dennisdoomen @lg2de, is there anything else to do for this PR?

No, waiting for @jnyrup to find some time to review the PR as well.

@benagain benagain force-pushed the anonymous-types-formatting branch from 54cebd5 to cf68b01 Compare March 28, 2023 14:47
Copy link
Member

@jnyrup jnyrup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work.
I really liked reviewing this and how we gradually improved it one step a time.

@jnyrup
Copy link
Member

jnyrup commented Mar 29, 2023

@dennisdoomen Will you have another look since the implementation changed some since your approval.

@dennisdoomen dennisdoomen self-requested a review March 30, 2023 05:22
Copy link
Member

@dennisdoomen dennisdoomen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓Wouldn't it make the output better readable if we add an empty line after a multi-line "Expected..." statement? Just like we do in C#.

@benagain sorry for some of the new comments. But as @jnyrup requested, I had a bit of a more thorough look at the PR (and the output we used to generate). Very happy with the improvements. We just need to make sure it's picture perfect.

@benagain
Copy link
Contributor Author

benagain commented Apr 2, 2023

❓Wouldn't it make the output better readable if we add an empty line after a multi-line "Expected..." statement? Just like we do in C#.

I'm not entirely sure what you mean here @dennisdoomen, is this the same as #2144 (comment)? If not, could you elaborate a little please?

@benagain sorry for some of the new comments. But as @jnyrup requested, I had a bit of a more thorough look at the PR (and the output we used to generate). Very happy with the improvements. We just need to make sure it's picture perfect.

No problem, I'm quite enjoying polishing it to get it right.

@dennisdoomen
Copy link
Member

I'm not entirely sure what you mean here @dennisdoomen, is this the same as #2144 (comment)? If not, could you elaborate a little please?

Yeah, I meant the same thing there.

@benagain benagain force-pushed the anonymous-types-formatting branch from a4c784f to daf9ac3 Compare April 2, 2023 09:27
@dennisdoomen
Copy link
Member

@benagain I assume you're still working on this PR. Ping me when you're done and I can conclude the review.

@benagain
Copy link
Contributor Author

benagain commented Apr 7, 2023

@benagain I assume you're still working on this PR. Ping me when you're done and I can conclude the review.

Hi @dennisdoomen, I found an annoying problem that has been tricky to resolve. As it currently stands a list of objects will be formatted with the list's opening brace and the first object's name on the same line as "Expected...", rather than on a new line:

  Expected stuff to be equal to {FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]]
      {
          Children = {1, 2, 3, 4}, 
  ...

This comes down to the interplay between EnumerableValueFormatter and FormattedObjectGraph and is challenging because simple, short lists should be formatted on the original line:

Expected list to be equal to {1,2,3,4}

I'm fairly close to a solution but I will be the first to say it's a touch convoluted. Maybe when I show the code someone will have a better idea.

@benagain benagain force-pushed the anonymous-types-formatting branch from 10b3abc to 0d74c41 Compare April 8, 2023 07:08
@benagain
Copy link
Contributor Author

benagain commented Apr 8, 2023

@dennisdoomen, @lg2de, @jnyrup, and anyone else interested!..

I'd like to draw your attention to the test When_the_object_is_a_generic_type_without_custom_string_representation_it_should_show_the_properties:

act.Should().Throw<XunitException>()
.And.Message.Should().Match(
"""
Expected stuff to be equal to
{
FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]]
{
Children = {1, 2, 3, 4},
Description = "Stuff_1",
StuffId = 1
},

I think this formatting is what we're after, especially the newline after "expected stuff to be", and indentation of the type name to match the type contents.

This particular stuff is a list of complex objects, and the EnumerableFormatter does not know whether or not to put that opening brace on a newline until it has formatted at least one child. Lists of integers should be formatted on a single line:

Expected numbers to be {1, 2, 3, 4}

I tried a few different things, then came up with PossibleMultilineFragment formatter which tracks where the brace should go in the output, and then insert it later on after we know if this enumerable is single-line or spans multiple lines. EnumerableFormatter was already making a "single or multi-line" decision so I think this follow that pattern. Inserting content into the graph post-hoc required more access to the internals of FormattedObjectGraph so I felt it should be a responsibility of that now.

Please have a look. It seems a bit overly complicated for what should be something simple but I have not yet thought of a better solution whilst keeping the desired formatting.

@lg2de
Copy link
Contributor

lg2de commented Apr 11, 2023

I like what I see in the new tests!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants