spockframework / spock

The Enterprise-ready testing and specification framework.
https://spockframework.org
Apache License 2.0
3.52k stars 0 forks source link

Mock Makers Extension not working with Spring #1960

Open avenue68 opened 1 month ago

avenue68 commented 1 month ago

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

AndreasTu commented 1 month ago

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?

avenue68 commented 1 month ago

@AndreasTu Thank you for checking. By the way what does "additional" interface mean? Just implemented interfaces?

AndreasTu commented 1 month ago

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.