The Problem
When I write tests in Java, I find it tedious to manually create objects and setting attributes. Code easily turns out like this:
var foo = new Foo("Satellite", 1000, true);
var bar = new Bar("Edward", "Scissorhands", List.of(foo));
// do stuff
assertEquals(foo, bar.findByName("Satellite"));
Here we're first creating a Foo
, which is then added to a Bar
. We then test that we can find the Foo
when we ask the Bar
to search for it.
It is clear that even though both Foo
and Bar
have other attributes, we're only interested in setting the name, and making sure that we can find it later. All the other ones are just in the way in this test.
Also, having to do this for every test is cumbersome and boring, and will probably at some point be the reason why I choose to not write a test. Boooh.
The Solution
What I normally do is to set up test object builders that makes it really easy to create new objects on the fly. For the above classes I would create two builder like this:
public class FooTestDataCreator {
private Faker faker = new Faker();
private String name = faker.name().lastName();
private int flurbs = faker.number().numberBetween(1,2000);
private boolean blarg = faker.bool();
public static FooTestDataCreator aFoo() {
return new FooTestDataCreator();
}
public FooTestDataCreator withName(String name) {
this.name = name;
return this;
}
public FooTestDataCreator withFlurbs(int flurbs) {
this.flurbs = flurbs;
return this;
}
public FooTestDataCreator withBlarg(boolean blarg) {
this.blarg = blarg;
return this;
}
public Foo build() {
return new Foo(name, flurbs, blarg);
}
}
public class BarTestDataCreator {
private Faker faker = new Faker();
private String firstName = faker.name().firstName();
private int lastName = faker.name().lastName();
private List<Foo> foos = List.of(aFoo().build());
public static BarTestDataCreator aBar() {
return new BarTestDataCreator();
}
public BarTestDataCreator withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public BarTestDataCreator withLastName(String lastName) {
this.lastName = lastName;
return this;
}
public BarTestDataCreator withFoos(List<Foo> foos) {
this.foos = foos;
return this;
}
public Bar build() {
return new Bar(firstName, lastName, foos);
}
}
With these two builders, the test code would look like this:
var foo = aFoo().withName("Satellite").build();
var bar = aBar().withFoos(List.of(foo)).build();
// do stuff
assertEquals(foo, bar.findByName("Satellite"));
It is not much less code if you're counting characters, but it states intent more clearly by only setting the value we actually care about.
The values that are not explicitly set gets a random value with the help of Faker. This means that we easily can create valid objects, and the random values assert that we're not accidentally writing code that just works for the hard coded test value.
Conclusion
By writing builders like above for my test objects, I find my tests easier to both read and write, and they're more reliable. It does take some time to write them, but I absolutely find it worth my time.