Spock/Groovy测试同时验证调用次数与异常失败,单独验证正常?
Spock测试同时验证方法调用次数与异常时失败的问题
问题场景
在Spock/Groovy测试中,当同一个测试用例同时验证方法调用次数和抛出的异常时,测试失败;但单独验证任意一项时,测试均可正常通过。
可正常通过的测试用例1(仅验证异常)
def "should throw AppointmentNotFoundException when appointment do not exist" () { given: long nonExistingAppointmentId = 69L when: appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") } findAppointmentQueryHandler.handle(nonExistingAppointmentId) then: thrown(AppointmentNotFoundException) }
可正常通过的测试用例2(仅验证方法调用次数)
def "should throw AppointmentNotFoundException when appointment do not exist" () { given: long nonExistingAppointmentId = 69L when: appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") } findAppointmentQueryHandler.handle(nonExistingAppointmentId) then: 1 * appointmentRepository.findById(69L) }
合并后失败的测试用例
def "should throw AppointmentNotFoundException when appointment do not exist" () { given: long nonExistingAppointmentId = 69L when: appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") } findAppointmentQueryHandler.handle(nonExistingAppointmentId) then: 1 * appointmentRepository.findById(69L) thrown(AppointmentNotFoundException) }
错误信息
Expected exception of type 'com.bbc.anotherhospital.exceptions.AppointmentNotFoundException', but no exception was thrown at org.spockframework.lang.SpecInternals.checkExceptionThrown(SpecInternals.java:84) at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:71) at com.bbc.anotherhospital.appointment.handlers.FindAppointmentQueryHandlerSpec.should throw AppointmentNotFoundException when appointment do not exist
依赖配置
<dependency> <groupId>org.apache.groovy</groupId> <artifactId>groovy-all</artifactId> <version>4.0.15</version> <type>pom</type> </dependency> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>2.4-M1-groovy-4.0</version> <scope>test</scope> </dependency>
问题原因及解决方案
原因
Spock中,交互断言(如1 * 方法调用)的优先级高于存根(>>)设置。当你在then块中添加1 * appointmentRepository.findById(69L)时,该断言会覆盖你之前在when块中设置的存根行为(抛出异常),此时findById方法会返回对应类型的默认值(如null),而非抛出异常,导致thrown断言失败。
解决方案
有两种可行的修正方式:
方式1:将存根行为与交互断言合并
把存根的抛出异常逻辑直接整合到交互断言中,确保调用次数和行为同时生效:
def "should throw AppointmentNotFoundException when appointment do not exist" () { given: long nonExistingAppointmentId = 69L when: findAppointmentQueryHandler.handle(nonExistingAppointmentId) then: 1 * appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") } thrown(AppointmentNotFoundException) }
方式2:将存根设置移至given块
将存根逻辑放在测试准备阶段的given块中,then块仅验证调用次数和异常:
def "should throw AppointmentNotFoundException when appointment do not exist" () { given: long nonExistingAppointmentId = 69L appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") } when: findAppointmentQueryHandler.handle(nonExistingAppointmentId) then: 1 * appointmentRepository.findById(nonExistingAppointmentId) thrown(AppointmentNotFoundException) }
内容的提问来源于stack exchange,提问作者michalbarnat
相关产品推荐
相关产品推荐

