如何优化AssertJ中领域模型与DTO的自定义匹配器以减少重复代码?
优化AssertJ自定义匹配器代码重复的方案
针对领域模型与DTO类的AssertJ自定义匹配器存在大量重复代码的问题,提供以下几种优化方案:
方案一:泛型抽象基类(推荐,类型安全)
将通用断言逻辑和模板代码抽象到泛型基类中,子类仅需实现特定类型的字段获取逻辑:
1. 定义泛型抽象基类
import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.Assertions; public abstract class AbstractValueAssert<SELF extends AbstractValueAssert<SELF, T>, T> extends AbstractAssert<SELF, T> { protected AbstractValueAssert(T actual, Class<?> selfType) { super(actual, selfType); } // 子类需实现该方法,提供从目标对象获取value的逻辑 protected abstract String extractValue(T actual); // 通用的hasValue断言方法 public SELF hasValue(String expected) { Assertions.assertThat(extractValue(actual)).isEqualTo(expected); return myself; } }
2. 领域模型匹配器子类
public class ExampleAssert extends AbstractValueAssert<ExampleAssert, Example> { private ExampleAssert(Example actual) { super(actual, ExampleAssert.class); } public static ExampleAssert assertThat(Example actual) { return new ExampleAssert(actual); } @Override protected String extractValue(Example actual) { return actual.getValue(); } }
3. DTO匹配器子类
public class ExampleDTOAssert extends AbstractValueAssert<ExampleDTOAssert, ExampleDTO> { private ExampleDTOAssert(ExampleDTO actual) { super(actual, ExampleDTOAssert.class); } public static ExampleDTOAssert assertThat(ExampleDTO actual) { return new ExampleDTOAssert(actual); } @Override protected String extractValue(ExampleDTO actual) { return actual.getValue(); } }
该方案完全复用了断言逻辑,子类仅需实现字段提取和基础模板代码,符合AssertJ的流畅API设计风格,且类型安全无反射。
方案二:接口默认方法(适配无法修改基类的场景)
通过定义包含默认断言方法的接口,让两个匹配器类实现该接口以复用逻辑:
1. 定义断言接口
import org.assertj.core.api.Assertions; public interface HasValueAssert<SELF> { // 子类需实现该方法,返回被断言的对象 Object getActual(); default SELF hasValue(String expected) { // 用AssertJ的extract方法反射获取value字段,无需修改业务类 Assertions.assertThat(getActual()).extracting("value").isEqualTo(expected); return (SELF) this; } }
2. 改造现有匹配器
public class ExampleAssert extends AbstractAssert<ExampleAssert, Example> implements HasValueAssert<ExampleAssert> { private ExampleAssert(Example actual) { super(actual, ExampleAssert.class); } public static ExampleAssert assertThat(Example actual) { return new ExampleAssert(actual); } @Override public Object getActual() { return actual; } }
public class ExampleDTOAssert extends AbstractAssert<ExampleDTOAssert, ExampleDTO> implements HasValueAssert<ExampleDTOAssert> { private ExampleDTOAssert(ExampleDTO actual) { super(actual, ExampleDTOAssert.class); } public static ExampleDTOAssert assertThat(ExampleDTO actual) { return new ExampleDTOAssert(actual); } @Override public Object getActual() { return actual; } }
如果允许修改业务类,可让Example和ExampleDTO实现一个统一的HasValue接口(包含getValue()方法),将接口中的getActual()改为返回HasValue类型,即可避免反射调用:
public interface HasValue { String getValue(); } // 业务类实现该接口后,断言接口可优化为: public interface HasValueAssert<SELF, T extends HasValue> { T getActual(); default SELF hasValue(String expected) { Assertions.assertThat(getActual().getValue()).isEqualTo(expected); return (SELF) this; } }
方案三:AssertJ Condition(无侵入式复用)
将通用断言逻辑封装为AssertJ的Condition,在匹配器中直接复用:
1. 定义通用Condition
import org.assertj.core.api.Condition; import java.lang.reflect.Method; public class ValueEqualsCondition extends Condition<Object> { private final String expectedValue; public ValueEqualsCondition(String expectedValue) { this.expectedValue = expectedValue; as("value equals '%s'", expectedValue); } @Override public boolean matches(Object actual) { try { Method getValueMethod = actual.getClass().getMethod("getValue"); String actualValue = (String) getValueMethod.invoke(actual); return expectedValue.equals(actualValue); } catch (Exception e) { throw new AssertionError("Failed to extract value from object", e); } } }
2. 在匹配器中使用Condition
public class ExampleAssert extends AbstractAssert<ExampleAssert, Example> { private ExampleAssert(Example actual) { super(actual, ExampleAssert.class); } public static ExampleAssert assertThat(Example actual) { return new ExampleAssert(actual); } public ExampleAssert hasValue(String expected) { matches(new ValueEqualsCondition(expected)); return this; } }
public class ExampleDTOAssert extends AbstractAssert<ExampleDTOAssert, ExampleDTO> { private ExampleDTOAssert(ExampleDTO actual) { super(actual, ExampleDTOAssert.class); } public static ExampleDTOAssert assertThat(ExampleDTO actual) { return new ExampleDTOAssert(actual); } public ExampleDTOAssert hasValue(String expected) { matches(new ValueEqualsCondition(expected)); return this; } }
该方案无需修改任何业务类或现有匹配器的结构,但使用了反射,适合无法修改原有代码的场景。
内容的提问来源于stack exchange,提问作者IceMajor
相关产品推荐
相关产品推荐

