Fix NoClassDefFoundError issue during web unit tests run

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)
comments powered by Disqus