Hamcrest - Elegant way to test complex object with samepropertyvaluesas
Asked Answered
T

4

11

I have quite complex object structure (with bunch of primitive fields and object references) and want to test all fields except -a few- of them. As an example;

ComplexObject actual = generateMagically("someInput");
ComplexObject expected = ActualFunction.instance.workMagically(actual);

// we want to be sure that workMagically() would create a new ComplexObject
// with some fields are different than "actual" object.

// assertThat(actual, samePropertyValuesAs(expected)); would check all fields.
// what I want is actually; - notice that "fieldName1" and "fieldName2" are 
// primitives belong to ComplexObject
assertThat(actual, samePropertyValuesExceptAs(expected, "fieldName1", "fieldName2"))

Since I don't want to check all fields manually, I believe there must be a way to write that test elegantly. Any ideas?

Cheers.

Therrien answered 26/2, 2016 at 10:47 Comment(5)
So you have two bag objects and want to perform deep comparison?Ogawa
Not sure to call them bag object, has many primitive fields and other object references. The fields I want to skip are primitive ones belong to ComplexObject. We can say that'll be a deep comparison.Therrien
Do you just want the Matcher implementation for samePropertyValuesExceptAs as an answer to this question? If so then you could just create a copy of org.hamcrest.beans.SamePropertyValuesAs<T> and add to it another constructor/static factory method that will remove the excluded properties from being tested.Tome
as a note, you could try assertj: it has a more fluent api, IDE completion, and in your case, you can write assertThat(actual).isEqualToIgnoringGivenFields(expected, "fieldName1", "fieldName2); : joel-costigliola.github.io/assertj/index.htmlImpediment
@JérémieB, I suggest you to put your comment as an answer because that's what I'm looking for this question. Thanks!Therrien
D
11

You should have a look at shazamcrest, a great Hamcrest extension that offers what you need.

assertThat(expected, sameBeanAs(expectedPerson).ignoring("fieldName1").ignoring("fieldName2"));

See https://github.com/shazam/shazamcrest#ignoring-fields

Deipnosophist answered 28/2, 2016 at 15:34 Comment(3)
If anyone is wondering where sameBeanAs is, it is in the com.shazam.shazamcrest.matcher.Matchers class.Ungrudging
I do need to note that at the time of this writing, shazamcrest no longer supports Java 17+ and yields "Unable to make field private final ... accessible".Quinate
Is there anything like this that supports Java 17+?Helban
M
6

Just pass the list of properties to ignore as 2nd parameter to samePropertyValuesAs.

Hamcrest matcher API

public static <B> Matcher<B> samePropertyValuesAs(B expectedBean, String... ignoredProperties)

e.g.

samePropertyValuesAs(salesRecord,"id")
Meadors answered 10/5, 2020 at 23:19 Comment(0)
T
0

In general I see two solutions if ComplexObject can be modified by yourself.

You could introduce an interface that represents the properties of ComplexObject that are being changed by ActualFunction. Then you can test that all properties of that new interface have changed. This would require that ComplexObject implements that new interface.

Another approach would be to replace the properties of ComplextObject that are changed by ActualFunction with a new property of a new type that contains all those properties. A better design would then be to let ActualFunction return an instance of the new type.

Tome answered 26/2, 2016 at 10:58 Comment(3)
Thanks SpaceTrucker. (1) I think implementing an interface in domain object just for test purpose is not a best practice. (2) For another approach, you actually offer to add a new property (I assume a new object) which has updated properties? Again changing the object just for testing, I guess no.Therrien
@Therrien this isn't only about testing, it is about making the effects of ActualFunction more visible and the function it represents more expressive. For example if the implementation of ActualFunction would change to also modify another property than the effect would be immediatly visible if using my second approach because the new type would get that new property.Tome
I actually don't need to put extra expression or visibility to workMagically() function about changing properties. Infact, we don't want to say anything explicit about updated fields and expect users of this class to be aware of this.Therrien
O
0

Last time I had a similar requirements I came to the conclusion that manually writing both code and tests to assert that some values are updated is inherently fagile and error-prone.

I externalized the fields in a bag object and generated the Java source files for both the bag class itself and the copier at compile time. This way you can test actual code (the generator) and have the actual definition of the domain in exactly one place, so the copy code can't be out-of-date.

The language to describe the property can be anything you are comfortable with, from JSON-schema to XML to Java itself (Java example follows - custom annotations are to be consumed from the generator)

public class MyBag {
  @Prop public int oh;
  @Prop public String yeah;
}
Ogawa answered 26/2, 2016 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.