Print object fields via reflection when toString() is not implemented - #3844
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3844 +/- ##
============================================
+ Coverage 86.84% 86.92% +0.08%
- Complexity 3044 3063 +19
============================================
Files 344 344
Lines 9172 9203 +31
Branches 1135 1138 +3
============================================
+ Hits 7965 8000 +35
+ Misses 917 916 -1
+ Partials 290 287 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
04792af to
8f62553
Compare
| } | ||
| } | ||
|
|
||
| private static String reflectiveToString(Object value) { |
There was a problem hiding this comment.
This is probably not nearly as robust as Apache commons ReflectionToStringBuilder but checking the source I don't think it would be trivial to pare down for this repo:
https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
There was a problem hiding this comment.
I think we don't need all the features of ReflectionToStringBuilder.
I would say, it's not "robust", it just allows configuration: "skip null values" etc. We don't need it.
| private static Iterable<Field> fields(Class<?> type) { | ||
| return Arrays.stream(type.getDeclaredFields()) | ||
| .filter(field -> !Modifier.isStatic(field.getModifiers())) | ||
| .filter(field -> field.getName().indexOf('$') < 0) |
There was a problem hiding this comment.
Good point, fixed.
There was a problem hiding this comment.
Looks like you need to push a new commit with the change
| return Arrays.stream(type.getDeclaredFields()) | ||
| .filter(field -> !Modifier.isStatic(field.getModifiers())) | ||
| .filter(field -> field.getName().indexOf('$') < 0) | ||
| .sorted(comparing(Field::getName)) |
There was a problem hiding this comment.
why would the order of type.getDeclaredFields() be non-deterministic? Not sure why sorting is necessary
There was a problem hiding this comment.
Java reflection doesn't guarantee the order of fields/methods.
From Class.getDeclaredFields javadoc:
The elements in the returned array are not sorted and are not in any particular order.
| } | ||
| } | ||
|
|
||
| private static String truncate(String text) { |
There was a problem hiding this comment.
what if the different value gets truncated? This wouldn't be very useful then. Ideally this would have some behavior to report only the differing fields, or ensure the difference isn't truncated
There was a problem hiding this comment.
While it's theoretically possible, it hopefully doesn't happen too often.
Anyway, it will be better than now. :)
Yes, I totally agree that ideally, Mockito should report the differences in the fields. For too long string, probably report the exact position of diff in the string. Maybe in another PR?..
|
This seems reasonable at first glance, and low risk if it's only scoped to |
|
@jselbo It's scoped not only to |
| checksums/ | ||
| /.tool-versions | ||
|
|
||
| /.claude/settings.local.json |
There was a problem hiding this comment.
No problems, I've removed the file.
P.S. Though, it's usually a common practice to ignore this file.
| assertThatThrownBy(() -> verify(mock).run(expected)) | ||
| .isInstanceOf(AssertionError.class) | ||
| .hasMessageContaining("Argument(s) are different! Wanted:") | ||
| .hasMessageContaining("Credentials[password=secret, username=john]") |
There was a problem hiding this comment.
I wonder if there is a privacy/security concern here. Suppose test data has real sensitive information (like reading some secret keys -- probably bad practice but I'm sure it happens). Now Mockito will print the private fields which would otherwise not be exposed if the class doesn't implement toString(). This could be a bad surprise for someone.
There was a problem hiding this comment.
Valid concern.
But I would say it should not be a problem in tests.
- Mockito is mostly used in unit-tests (and sometimes integration tests). Usually people use test data, not real sensitive data in tests.
- And if they use some real passwords in tests, they should be ready for the risk that these passwords will appear in logs, test reports or CI notifications.
- Usually Jenkins, GitHub Actions etc. do mask the secure parameters like password.
In verification failure messages, Mockito uses `toString()` method to print out the argument object. For objects not having their own `toString()`, the useless description like "MyClass@12345" was generated (the default `Object.toString()` implementation). Now, ValuePrinter reflects over an object's declared fields when its class doesn't override `toString()`, thus producing readable description of the object. Too long field values are truncated.
b5b83a9 to
f785fa9
Compare
|
Ok I'm comfortable merging this. Thanks for this improvement! |
Motivation
In my project, it often happens that value object doesn't have its own
toString()method. And comes from a JAR that I cannot easily change.For such objects, it's highly inconvenient to debug failing tests showing just
MyClass@12345in failure message.@TimvdLippe @raphw
The problem
In verification failure messages, Mockito uses
toString()method to print out the argument object. For objects not having their owntoString(), the useless description like "MyClass@12345" was generated (the defaultObject.toString()implementation).The solution
Now, ValuePrinter reflects over an object's declared fields when its class doesn't override
toString(), thus producing readable description of the object. Too long field values are truncated.Example of error message:
Where
Checklist
including project members to get a better picture of the change
commit is meaningful and help the people that will explore a change in 2 years
./gradlew spotlessApplyfor auto-formatting)Fixes #<issue number>in the description if relevantFixes #<issue number>if relevant