为何Mockito测试中,ZonedDateTime结合Dozer会抛NoSuchMethodException?
我近期编写JUnit测试用例时遇到问题:待测试方法使用DozerBeanMapper复制字段,我使用@Spy而非@Mock注入DozerBeanMapper(@Mock生成的假对象会导致断言异常)。
第一种实现方式(执行测试抛出异常)
class A { @Autowired DozerBeanMapper mapper; Entity method(Request req) { Entity ent = new Entity(); req.setCreatedOn(ZonedDateTime.now()); mapper.map(req, ent); return ent; } }
抛出的异常信息
org.dozer.MappingException: java.lang.NoSuchMethodException: java.time.LocalDateTime.<init>() at org.dozer.util.MappingUtils.throwMappingException(MappingUtils.java:82) ~[dozer-5.4.0.jar:na] at org.dozer.factory.ConstructionStrategies$ByConstructor.newInstance(ConstructionStrategies.java:261) ~[dozer-5.4.0.jar:na] at org.dozer.factory.ConstructionStrategies$ByConstructor.create(ConstructionStrategies.java:245) ~[dozer-5.4.0.jar:na] at org.dozer.factory.DestBeanCreator.create(DestBeanCreator.java:65) ~[dozer-5.4.0.jar:na] at org.dozer.MappingProcessor.mapCustomObject(MappingProcessor.java:489) [dozer-5.4.0.jar:na] at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:446) [dozer-5.4.0.jar:na] at org.dozer.MappingProcessor.mapFromFieldMap(MappingProcessor.java:342) [dozer-5.4.0.jar:na] at org.dozer.MappingProcessor.mapField(MappingProcessor.java:288) [dozer-5.4.0.jar:na]
调整后的实现方式(运行正常)
class A { @Autowired DozerBeanMapper mapper; Entity method(Request req){ Entity ent = new Entity(); mapper.map(req, ent); ent.setCreatedOn(ZonedDateTime.now()); return ent; } }
测试用例代码
class ATest { @InjectMocks A aService; @Spy DozerBeanMapper mapper; @Test void methodTest(){ aService.method(req); } }
异常原因分析
核心原因是你使用的Dozer 5.4.0版本不支持Java 8的时间API(如ZonedDateTime、LocalDateTime)。
第一种写法中,你先给req设置了ZonedDateTime类型的createdOn字段,随后调用mapper.map(req, ent)时,Dozer在处理这个时间字段的映射逻辑时,会尝试通过无参构造实例化LocalDateTime(可能是因为Entity的createdOn字段类型为LocalDateTime,或者Dozer内部类型转换逻辑的问题)。但Java 8的时间类(包括LocalDateTime)都没有公共的无参构造方法,只能通过静态工厂方法创建实例,因此触发了NoSuchMethodException,最终包装为MappingException抛出。
而第二种写法中,你先执行Dozer映射(此时req的createdOn字段未被赋值,或Dozer映射时跳过了该字段),之后手动给ent设置ZonedDateTime值,绕开了Dozer对Java 8时间字段的映射逻辑,因此不会触发异常。
另外,你用@Spy注入的是真实的DozerBeanMapper实例,它会执行完整的真实映射逻辑,所以会暴露这个兼容性问题;而@Mock生成的是假对象,不会执行真实的字段映射逻辑,因此不会报错,但会导致后续断言失败(因为目标对象的字段未被正确复制)。
解决建议
- 升级Dozer版本到支持Java 8时间API的版本(如Dozer 6.x及以上,或者引入
dozer-jdk8扩展包) - 保持第二种写法,手动处理Java 8时间字段的赋值,避免通过Dozer映射这类字段
- 在Dozer配置中添加自定义的Java 8时间类型转换器,覆盖默认的映射逻辑
内容的提问来源于stack exchange,提问作者Sachi97

