In some SAP Commerce setups, there is an unusual problem where web unit tests occasionally fail when run with a suppressed tenant. These tests encounter random failures while creating mock classes, resulting in exceptions like java.lang.NoClassDefFoundError: Could not initialize class org.mockito.codegen.
Problem Description
The occurrence of java.lang.NoClassDefFoundError
during the execution of web tests using either ant allwebtests -Dtestclasses.suppress.junit.tenant=true
or ant ci-ut -Dtestclasses.all.custom.extensions=true
(improved ant targets) is directly linked to problems with mock creation. From stack trace, it can be seen that InlineDelegateByteBuddyMockMaker
is used by MockUtil#createMock
for mock creation and is failing with java.lang.NoClassDefFoundError
. Most probably, that exception is caused by implementation specifics of web classpath handling in de.hybris.ant.taskdefs.yunit.BatchTest
ant task.
Technical Solution
Unfortunately, the same issue happens with the most recent versions of mockito-core, mockito-inline, and byte-buddy, so the only available option for resolution is to utilize another mock class maker instead of InlineDelegateByteBuddyMockMaker
. As a replacement, ProxyMockMaker
would be used, which uses the JVM default Proxy#newProxyInstance
to create a new class by interface.
In SAP Commerce, the mockito-inline is present in platform libraries, what alters the default behavior of mockito-core to use InlineDelegateByteBuddyMockMaker
instead of ProxyMockMaker
. This is achieved by creating a file mockito-extensions/org.mockito.plugins.MockMaker with the content mock-maker-inline. Applying the same approach by creating a similar file mockito-extensions/org.mockito.plugins.MockMaker in the core extension with the content mock-maker-proxy does not work, as the implementation in org.mockito.internal.configuration.plugins.PluginInitializer#loadImpl
would prioritize the first loaded file, which would be the one provided by the mockito-inline library.
Luckily, mockito-core library provides an extension point mockito-extensions/org.mockito.plugins.PluginSwitch, which allow to disable plugins programmatically. That can be used to disable mock-maker-inline plugin, so mockito-core would utilize the default value of mock-maker-proxy, which would lead to the usage of ProxyMockMaker
.
Implementation Details
Firstly, an implementation for the PluginSwitch
interface should be created to ignore the mock-maker-inline plugin in the core extension:
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.blog.core.mockito;
import org.mockito.plugins.PluginSwitch;
@SuppressWarnings("unused")
public class NoInlineMocksPluginSwitch implements PluginSwitch {
@Override
public boolean isEnabled(String option) {
// ! "mock-maker-inline" leads to flaky web unittests with suppressed tenant.
return !"mock-maker-inline".equals(option);
}
}
|
After that default mockito implementation should be replaced with custom one by creating a file resources/mockito-extensions/org.mockito.plugins.PluginSwitch
in the core extension with content:
1
|
com.blog.core.mockito.NoInlineMocksPluginSwitch
|
Appendix
Example of stack trace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
Could not initialize class org.mockito.codegen.CartFacade$MockitoMock$549801534
java.lang.NoClassDefFoundError: Could not initialize class org.mockito.codegen.CartFacade$MockitoMock$549801534
at jdk.internal.reflect.GeneratedSerializationConstructorAccessor4.newInstance(Unknown Source)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
at org.mockito.internal.creation.instance.ObjenesisInstantiator.newInstance(ObjenesisInstantiator.java:22)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.doCreateMock(InlineDelegateByteBuddyMockMaker.java:372)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createMock(InlineDelegateByteBuddyMockMaker.java:330)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMock(InlineByteBuddyMockMaker.java:58)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:53)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:84)
at org.mockito.Mockito.mock(Mockito.java:1964)
at org.mockito.internal.configuration.MockAnnotationProcessor.processAnnotationForMock(MockAnnotationProcessor.java:66)
at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:27)
at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:24)
at org.mockito.internal.configuration.IndependentAnnotationEngine.createMockFor(IndependentAnnotationEngine.java:45)
at org.mockito.internal.configuration.IndependentAnnotationEngine.process(IndependentAnnotationEngine.java:73)
at org.mockito.internal.configuration.InjectingAnnotationEngine.processIndependentAnnotations(InjectingAnnotationEngine.java:74)
at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:48)
at org.mockito.MockitoAnnotations.openMocks(MockitoAnnotations.java:82)
at org.mockito.MockitoAnnotations.initMocks(MockitoAnnotations.java:100)
at de.hybris.platform.textfieldconfiguratortemplateaddon.controllers.pages.ProductTextfieldConfiguratorControllerTest.initialize(ProductTextfieldConfiguratorControllerTest.java:125)
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 de.hybris.ant.taskdefs.yunit.JUnitTestRunner.run(JUnitTestRunner.java:649)
at de.hybris.ant.taskdefs.yunit.JUnitTestRunner.launch(JUnitTestRunner.java:1374)
at de.hybris.ant.taskdefs.yunit.JUnitTestRunner.main(JUnitTestRunner.java:1164)
|