Mock Makers Extension not working with Spring
Describe the bug
My spock test results in an exception occurrence when I try to mock a final class which is registered as a Spring bean.
I have a mockito dependency in the classpath and enabled it by SpockConfiguration.groovy.
And this problem not occur when the mocked class is non-final or Spring is not used.
To Reproduce
The code bellow reproduces the problem.
@ContextConfiguration(classes = [MyConfiguration]) // Commenting out this line hides the problem.
class ExampleSpec extends Specification {
@SpringBean
MyFinalClass myFinalClass = Mock()
def "example"() {
given: "a mock for MyFinalClass"
1 * myFinalClass.myMethod() >> "Mocked method called."
when: "calling myMethod"
def result = myFinalClass.myMethod()
then: "the mock is called"
result == "Mocked method called."
}
// Making this class not final hides the problem.
final class MyFinalClass {
final String myMethod() {
return "Real method called."
}
}
@Configuration
class MyConfiguration {
@Bean
String myFinalClass(MyFinalClass myFinalClass) {
return "a bean"
}
}
}
Expected behavior
Exception not thrown and can mock final classes properly.
Actual behavior
CannotCreateMockException is thrown.
The stacktrace is bellow.
java.lang.IllegalStateException: Failed to load ApplicationContext for [MergedContextConfiguration@17aa06eb testClass = ExampleSpec, locations = [], classes = [ExampleSpec.MyConfiguration], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = [], contextCustomizers = [org.spockframework.spring.mock.SpockContextCustomizer@9d08b8c2, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@68838767, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@36dc6b9, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7e5e7753, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0], contextLoader = org.springframework.test.context.support.DelegatingSmartContextLoader, parent = null]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:204)
at org.spockframework.spring.SpringTestContext.getApplicationContext(SpringTestContext.java:49)
at org.spockframework.spring.SpringMockTestExecutionListener.injectSpies(SpringMockTestExecutionListener.java:66)
at org.spockframework.spring.SpringMockTestExecutionListener.prepareTestInstance(SpringMockTestExecutionListener.java:62)
at org.spockframework.spring.AbstractSpringTestExecutionListener.prepareTestInstance(AbstractSpringTestExecutionListener.java:32)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
at org.spockframework.spring.SpringTestContextManager.prepareTestInstance(SpringTestContextManager.java:56)
at org.spockframework.spring.SpringInterceptor.interceptInitializerMethod(SpringInterceptor.java:46)
at org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:24)
at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:122)
at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:430)
at org.spockframework.runtime.PlatformSpecRunner.runInitializer(PlatformSpecRunner.java:265)
at org.spockframework.runtime.PlatformSpecRunner.runInitializer(PlatformSpecRunner.java:260)
at org.spockframework.runtime.IterationNode.prepare(IterationNode.java:32)
at org.spockframework.runtime.IterationNode.prepare(IterationNode.java:13)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor.execute(NodeTestTask.java:226)
at org.spockframework.runtime.SimpleFeatureNode.execute(SimpleFeatureNode.java:58)
at org.spockframework.runtime.SimpleFeatureNode.execute(SimpleFeatureNode.java:19)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.spockframework.runtime.SpockNode.sneakyInvoke(SpockNode.java:40)
at org.spockframework.runtime.FeatureNode.lambda$around$0(FeatureNode.java:73)
at org.spockframework.runtime.PlatformSpecRunner.lambda$createMethodInfoForDoRunFeature$4(PlatformSpecRunner.java:203)
at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:157)
at org.spockframework.runtime.PlatformSpecRunner.invokeRaw(PlatformSpecRunner.java:439)
at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:422)
at org.spockframework.runtime.PlatformSpecRunner.runFeature(PlatformSpecRunner.java:193)
at org.spockframework.runtime.FeatureNode.around(FeatureNode.java:73)
at org.spockframework.runtime.FeatureNode.around(FeatureNode.java:30)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.spockframework.runtime.SpockNode.sneakyInvoke(SpockNode.java:40)
at org.spockframework.runtime.SpecNode.lambda$around$0(SpecNode.java:72)
at org.spockframework.runtime.PlatformSpecRunner.lambda$createMethodInfoForDoRunSpec$0(PlatformSpecRunner.java:66)
at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:157)
at org.spockframework.runtime.PlatformSpecRunner.invokeRaw(PlatformSpecRunner.java:439)
at org.spockframework.runtime.PlatformSpecRunner.invoke(PlatformSpecRunner.java:422)
at org.spockframework.runtime.PlatformSpecRunner.runSpec(PlatformSpecRunner.java:59)
at org.spockframework.runtime.SpecNode.around(SpecNode.java:72)
at org.spockframework.runtime.SpecNode.around(SpecNode.java:12)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:119)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:94)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:89)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at jdk.proxy2/jdk.proxy2.$Proxy5.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.spockframework.mock.CannotCreateMockException: Cannot create mock for class ExampleSpec$MyFinalClass.
mockito: Cannot mock final classes with additional interfaces.
java-proxy: Cannot mock classes.
byte-buddy: Cannot mock final classes.
cglib: Cannot mock final classes.
at org.spockframework.mock.runtime.MockMakerRegistry.createWithAppropriateMockMaker(MockMakerRegistry.java:198)
at org.spockframework.mock.runtime.MockMakerRegistry.makeMockInternal(MockMakerRegistry.java:153)
at org.spockframework.mock.runtime.MockMakerRegistry.makeMock(MockMakerRegistry.java:125)
at org.spockframework.spring.mock.SpockSpringProxyCreator.create(SpockSpringProxyCreator.java:38)
at org.spockframework.spring.mock.SpockDefinition.createMock(SpockDefinition.java:78)
at org.spockframework.spring.mock.SpockMockPostprocessor.createMock(SpockMockPostprocessor.java:154)
at org.spockframework.spring.mock.SpockMockPostprocessor.registerMock(SpockMockPostprocessor.java:122)
at org.spockframework.spring.mock.SpockMockPostprocessor.register(SpockMockPostprocessor.java:104)
at org.spockframework.spring.mock.SpockMockPostprocessor.postProcessBeanFactory(SpockMockPostprocessor.java:97)
at org.spockframework.spring.mock.SpockMockPostprocessor.postProcessBeanFactory(SpockMockPostprocessor.java:90)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:788)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:606)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:221)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:110)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:212)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152)
... 105 common frames omitted
Java version
openjdk version "17.0.10" 2024-01-16 LTS OpenJDK Runtime Environment Corretto-17.0.10.7.1 (build 17.0.10+7-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.10.7.1 (build 17.0.10+7-LTS, mixed mode, sharing)
Buildtool version
Gradle 8.4
Build time: 2023-10-04 20:52:13 UTC Revision: e9251e572c9bd1d01e503a0dfdf43aedaeecdc3f
Kotlin: 1.9.10 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 17.0.10 (Amazon.com Inc. 17.0.10+7-LTS) OS: Mac OS X 13.5 aarch64
What operating system are you using
Mac
Additional context
No response
A @SpringBean gets automatically the marker interface org.spockframework.spring.mock.SpockSpringProxy added to the mock instance, in the class SpockSpringProxyCreator.
But final classes with additonal interfaces are not mockable (also not by mockito itself) due to technical limitations.
See also error message in the exception: "mockito: Cannot mock final classes with additional interfaces."
So as you already mentioned as a workaround remove the final.
@leonard84 Maybe we could remove the automatical addition of the SpockSpringProxy marker interface.
Do you know why this interface was introduced, I don't find production code where a mock instance is checked against. I find only checks in tests.
Do you think we could remove the marker interface all together?
@AndreasTu Thank you for checking. By the way what does "additional" interface mean? Just implemented interfaces?
You can specify that a mock should implement additional Interfaces in addition to the mock class. So you could create a mock instance of YourClass and also implement e.g. Runnable and the later cast the mock instance to Runnable.
This is done by internally subclassing YourClass and implementing the additional interfaces automatically. That is also the reason why final classes do not work, because we can't subclass a final class in the JVM.