Spock测试中无法使用Lombok @Value修饰的不可变DTO的解决方案咨询
你遇到的这个问题很典型——Lombok的@Value会把类标记为final,而Spock默认的Java Mock(基于CGLIB或Mockito)无法mock final类。不用移除@Value也能解决,这里有几个实用的方案:
方案1:使用Spock的GroovyMock(最推荐)
Spock的GroovyMock是基于Groovy语言特性实现的mock,它可以轻松mock final类、静态方法这些Java Mock搞不定的场景。你只需要把mock的方式换成GroovyMock即可:
def "test user factory with GroovyMock"() { given: def userDto = GroovyMock(UserDto) def user = new User(id: 1L, username: "test") def userFactory = new UserFactory() when: def result = userFactory.create(userDto) then: 1 * userFactory.create(userDto) >> user result == user }
这个方案不需要修改任何生产代码,完全在测试层面解决问题,是最简洁的选择。
方案2:通过Lombok配置让@Value不生成final类(不推荐)
如果你能接受牺牲一点不可变性(不推荐生产环境这么做,除非有特殊需求),可以通过lombok.config文件修改@Value的默认行为:
在项目根目录创建lombok.config文件,添加以下内容:
lombok.value.isFinal=false
这样@Value就不会把UserDto标记为final了,同时依然保留字段private final、生成equals/hashCode/toString等特性。但要注意,这样类可以被继承,可能破坏不可变DTO的设计初衷。
方案3:为DTO定义接口,Mock接口而非实现类
如果你的测试场景更倾向于面向接口编程,可以给UserDto定义一个接口,让DTO实现它,测试时mock接口:
首先定义接口:
public interface UserDtoInterface { Long getId(); String getUsername(); }
然后修改DTO实现接口:
@Value @Builder public class UserDto implements UserDtoInterface { Long id; String username; }
测试时mock接口:
def "test user factory with interface mock"() { given: def userDto = Mock(UserDtoInterface) def user = new User(id: 1L, username: "test") def userFactory = new UserFactory() when: def result = userFactory.create(userDto) then: 1 * userFactory.create(userDto) >> user result == user }
这个方案需要修改生产代码,但符合面向接口的设计原则,适合长期维护的项目。
方案4:使用PowerMock扩展Spock的Mock能力
PowerMock可以通过字节码修改技术mock final类,不过配置相对复杂一些。首先需要添加PowerMock的依赖(根据你的构建工具调整,比如Maven/Gradle),然后在测试类上添加注解:
@RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(Sputnik.class) @PrepareForTest(UserDto.class) class UserFactorySpec extends Specification { def "test with PowerMock"() { given: def userDto = PowerMockito.mock(UserDto.class) def user = new User(id: 1L, username: "test") def userFactory = new UserFactory() when: def result = userFactory.create(userDto) then: PowerMockito.when(userFactory.create(userDto)).thenReturn(user) result == user } }
注意PowerMock和Spock的版本兼容性,这个方案适合需要mock多个final类/静态方法的复杂场景,但会增加测试的复杂度。
内容的提问来源于stack exchange,提问作者k13i

