I have a big class Customer for which I am using groovy's @Builder annotation to generate builder classes. This works fine but while writing unit tests what I want to do is to define a customer with all the default values and then from my test I can pass some overrides using the builder class. I do not want to pass the field names for overwriting the fields because that is error prone.
Below is my builder class
@Builder(builderStrategy = ExternalStrategy, forClass = Customer.class, includeSuperProperties = true)
class CustomerBuilder {
}
This is my test builder class it accepts a parameter of class CustomerBuilder and calling method from unit test, is there any way I can merge these two builder to generate a customer object which has default values and the overrides
class TestDataBuilder {
public TestDataBuilder withDefaultCustomerOverrides(CustomerBuilder customerBuilder) {
CustomerBuilder customerBuilderDefault = new CustomerBuilder()
.modifiedDateTime(LocalDateTime.now())
.custName("Test Client")
//somehow merge the two builders
return customerBuilderDefault.build()
}
}
TestDataBuilder testDataBuilder = new TestDataBuilder()
testDataBuilder.withDefaultCustomerOverrides(new CustomerBuilder().custName("Custom Test Client"))
The builders are just containers for the data soon to be used to create the real object. With the default builders there is no knowledge in the builder object, whether a value was set or not (I'm sure, you can create this via a strategy you have to create first most likely yourself).
So we can not rely on the data inside the builder to tell us, what
values have been set: was id(null)
called to set the id
to null
,
or is it the initial default? So how to merge those two containers?
So instead of passing a builder instance to merge with the default builder instance, pass in a function, that transforms the builder. The functions for common, non-default setups you also can keep around and then compose/chain them.
E.g. (see the XXX
comments)
import java.time.Instant
import groovy.transform.builder.*
import groovy.transform.ToString
import java.util.function.Function
@ToString
class Customer {
Long id
String name
String comment
Instant createdTS
Instant modifiedTS
}
@Builder(builderStrategy = ExternalStrategy, forClass = Customer.class, includeSuperProperties = true)
class CustomerBuilder {
}
// XXX: pass in a function for the transformation and apply it on the default builder
Customer withDefaults(Function<CustomerBuilder,CustomerBuilder> transformBuilder) {
transformBuilder.apply(
new CustomerBuilder()
.name("Default")
.createdTS(Instant.now())
).build()
}
println withDefaults {
it.id(42)
.modifiedTS(Instant.parse('2020-01-01T00:00:00.0Z'))
} // → Customer(42, Default, null, 2024-04-27T08:13:30.083152202Z, 2020-01-01T00:00:00Z)
// XXX: extract common configs, that are not default
Function withComment = {
it.comment("Comment")
}
println withDefaults(withComment) // → Customer(null, Default, Comment, 2024-04-27T08:13:30.056778031Z, null)
// XXX: combine common builders with other overrides
println withDefaults(withComment.andThen {
it.id(666)
}) // → Customer(666, Default, Comment, 2024-04-27T08:13:30.056778031Z, null)