Groovy合并两个Builder类:单元测试实现默认值与覆写
问题
我有一个大型Customer类,使用Groovy的@Builder注解生成Builder类,运行正常。编写单元测试时,希望先定义包含所有默认值的Customer对象,再通过Builder传入覆写值,且不想手动传入字段名(易出错)。
现有Builder类代码:
@Builder(builderStrategy = ExternalStrategy, forClass = Customer.class, includeSuperProperties = true) class CustomerBuilder { }
测试用的TestDataBuilder类接收CustomerBuilder参数,希望合并默认Builder与传入的覆写Builder,生成包含默认值和覆写值的Customer对象,请问如何实现?
TestDataBuilder代码:
class TestDataBuilder { public TestDataBuilder withDefaultCustomerOverrides(CustomerBuilder customerBuilder) { CustomerBuilder customerBuilderDefault = new CustomerBuilder() .modifiedDateTime(LocalDateTime.now()) .custName("Test Client") // 需合并两个Builder return customerBuilderDefault.build() } } TestDataBuilder testDataBuilder = new TestDataBuilder() testDataBuilder.withDefaultCustomerOverrides(new CustomerBuilder().custName("Custom Test Client"))
解决方案
核心思路是提取覆写Builder中已设置的非空属性,覆盖到默认Builder的对应属性上,利用Groovy的动态特性自动处理所有字段,无需手动指定。
修改后的TestDataBuilder实现如下:
import java.time.LocalDateTime class TestDataBuilder { public Customer withDefaultCustomerOverrides(CustomerBuilder customerBuilder) { // 初始化默认Builder并设置默认值 CustomerBuilder customerBuilderDefault = new CustomerBuilder() .modifiedDateTime(LocalDateTime.now()) .custName("Test Client") // 遍历覆写Builder的所有属性,将非空值覆盖到默认Builder customerBuilder.metaClass.properties.each { prop -> // 跳过Groovy内置的元属性,只处理业务字段 if (!['class', 'metaClass'].contains(prop.name)) { def overrideValue = prop.getProperty(customerBuilder) if (overrideValue != null) { customerBuilderDefault.setProperty(prop.name, overrideValue) } } } return customerBuilderDefault.build() } } // 测试调用示例 TestDataBuilder testDataBuilder = new TestDataBuilder() Customer finalCustomer = testDataBuilder.withDefaultCustomerOverrides( new CustomerBuilder().custName("Custom Test Client") )
说明
- 利用Groovy的
metaClass.properties自动遍历所有字段,彻底避免手动写字段名的出错风险 - 仅覆盖非空值,确保默认值不会被覆写Builder中的空值覆盖
- 支持父类属性(因为Builder注解设置了
includeSuperProperties = true),无需额外处理
优化方案(针对Builder内部结构)
如果Groovy生成的Builder内部用特定容器存储已设置的字段(比如$fields Map),可以直接操作这个容器提升效率:
// 先通过println(customerBuilder.dump())确认内部存储字段的属性名 customerBuilder.$fields.each { key, value -> if (value != null) { customerBuilderDefault.$fields[key] = value } }
内容的提问来源于stack exchange,提问作者Ambuj Jauhari
相关产品推荐
相关产品推荐

