assertj icon indicating copy to clipboard operation
assertj copied to clipboard

Calling final methods through a custom assertion, which is inherited from AbstractListAssert, results in a java.lang.ClassCastException

Open AlexeyAkentyev opened this issue 9 months ago • 7 comments

Describe the bug Calling final methods through a custom assertion, which is inherited from AbstractListAssert, results in a java.lang.ClassCastException. In fact, any call to final methods inherited from the underlying Abstract Assert class leads to a java.lang.ClassCastException.

Could you advise on any workaround or point out where I might be handling the custom assertion implementation incorrectly?

  • assertj core version: 3.27.3
  • java version: 1.17
  • groovy version: 4.0.18
  • test framework version: spock 2.3-groovy-4.0
  • os (if relevant): MacOS/Linux

Test case reproducing the bug

I've implemented an example project that describes the issue: https://github.com/AlexeyAkentyev/assertj-custom-assertions-example/blob/d13162c1537ec1f7ead34df42294a5d9640a7cce/src/test/groovy/org/example/custom/assertions/tests/DomainObjectsAssertionSpec.groovy#L136

Project contains example of domain related assertions. Test fails with the error: Caused by: java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Lorg.example.custom.assertions.domain.DomainObject; ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; [Lorg.example.custom.assertions.domain.DomainObject; is in unnamed module of loader 'app')

AlexeyAkentyev avatar Mar 14 '25 09:03 AlexeyAkentyev

An equivalent JUnit reproducer that can be executed in assertj-core-groovy:

package org.example.custom.assertions.tests

import org.example.custom.assertions.DomainSoftAssertions
import org.example.custom.assertions.domain.DomainObject
import org.junit.jupiter.api.Test

import static org.assertj.core.api.BDDAssertions.thenNoException

class MyTest {

  @Test
  void test() {
    // GIVEN
    def actualDomainObjects = [new DomainObject('actual', 1),
                               new DomainObject('actual', 2)]
    // WHEN/THEN
    thenNoException().isThrownBy {
      DomainSoftAssertions.assertSoftly { softly ->
        softly.assertThatDomainObjects(actualDomainObjects)
          .hasExpectedDomainObjects()
          .containsExactlyInAnyOrderElementsOf(actualDomainObjects)
      }
    }
  }

}

which fails with:

java.lang.AssertionError: 
Expecting code not to raise a throwable but caught
  "java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Lorg.example.custom.assertions.domain.DomainObject; ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; [Lorg.example.custom.assertions.domain.DomainObject; is in unnamed module of loader 'app')
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$P47T2xhc.containsExactlyInAnyOrderForProxy(Unknown Source)
	at org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrder(AbstractIterableAssert.java:433)
	at org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrderElementsOf(AbstractIterableAssert.java:449)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$P47T2xhc.containsExactlyInAnyOrderElementsOf$accessor$nyd4a6MD(Unknown Source)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$P47T2xhc$AssertJ$SoftProxies$nIaYve8G.call(Unknown Source)
	at org.assertj.core.api.ErrorCollector.intercept(ErrorCollector.java:56)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$P47T2xhc.containsExactlyInAnyOrderElementsOf(Unknown Source)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$P47T2xhc.containsExactlyInAnyOrderElementsOf(Unknown Source)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest$_should_accept_triple_single_quoted_strings_closure1$_closure2.doCall(MyTest.groovy:31)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:280)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1007)
	at groovy.lang.Closure.call(Closure.java:433)
	at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:52)
	at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:113)
	at jdk.proxy1/jdk.proxy1.$Proxy18.accept(Unknown Source)
	at org.assertj.core.api.SoftAssertionsProvider.assertSoftly(SoftAssertionsProvider.java:119)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.DomainSoftAssertions.assertSoftly(DomainSoftAssertions.groovy:11)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest$_should_accept_triple_single_quoted_strings_closure1.doCall(MyTest.groovy:30)
	at org.example.custom.assertions.tests.MyTest$_should_accept_triple_single_quoted_strings_closure1.doCall(MyTest.groovy)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:280)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1007)
	at groovy.lang.Closure.call(Closure.java:433)
	at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:52)
	at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:113)
	at jdk.proxy2/jdk.proxy2.$Proxy15.call(Unknown Source)
	at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:63)
	at org.assertj.core.api.NotThrownAssert.isThrownBy(NotThrownAssert.java:43)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest.should_accept_triple_single_quoted_strings(MyTest.groovy:29)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:775)
	at org.junit.platform.commons.support.ReflectionSupport.invokeMethod(ReflectionSupport.java:479)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:161)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:152)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:91)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:112)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:94)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:87)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:216)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:212)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156)
	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:146)
	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:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	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:160)
	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:146)
	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:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	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:160)
	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:146)
	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:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	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:201)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:170)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:94)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:59)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:142)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:58)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85)
	at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
	at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$1(InterceptingLauncher.java:39)
	at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
	at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:38)
	at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
"

	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest.should_accept_triple_single_quoted_strings(MyTest.groovy:29)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

scordio avatar Mar 14 '25 23:03 scordio

The previous stack trace fails at:

org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$P47T2xhc.containsExactlyInAnyOrderForProxy(Unknown Source)

so apparently the issue is in the ByteBuddy proxy, although the stack trace reported by Spock doesn't mention it.

When changing the test to:

  @Test
  void test() {
    // GIVEN
    def actualDomainObjects = [new DomainObject('actual', 1),
                               new DomainObject('actual', 2)]
    // WHEN/THEN
    thenNoException().isThrownBy {
      assertThatDomainObjects(actualDomainObjects)
          .hasExpectedDomainObjects()
          .containsExactlyInAnyOrderElementsOf(actualDomainObjects)
    }
  }

no failure happens.

To completely rule out issues with final methods, I changed:

https://github.com/assertj/assertj/blob/fae7ffd81ae425d05e95fdecbf4c96ea77d75857/assertj-core/src/main/java/org/assertj/core/api/AbstractIterableAssert.java#L447-L450

to:

  @Override
  public SELF containsExactlyInAnyOrderElementsOf(Iterable<? extends ELEMENT> values) {
    return containsExactlyInAnyOrderForProxy(toArray(values));
  }

and the test case still fails:

java.lang.AssertionError: 
Expecting code not to raise a throwable but caught
  "java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Lorg.example.custom.assertions.domain.DomainObject; ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; [Lorg.example.custom.assertions.domain.DomainObject; is in unnamed module of loader 'app')
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$MiKaD12S.containsExactlyInAnyOrderForProxy(Unknown Source)
	at org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrderElementsOf(AbstractIterableAssert.java:449)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$MiKaD12S.containsExactlyInAnyOrderElementsOf$accessor$Bfn0c0HO(Unknown Source)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$MiKaD12S$AssertJ$SoftProxies$tY5XWFJH.call(Unknown Source)
	at org.assertj.core.api.ErrorCollector.intercept(ErrorCollector.java:56)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$MiKaD12S.containsExactlyInAnyOrderElementsOf(Unknown Source)
	at org.example.custom.assertions.domain.assertions.ListDomainObjectsAssertion$ByteBuddy$MiKaD12S.containsExactlyInAnyOrderElementsOf(Unknown Source)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest$_test_closure1$_closure2.doCall(MyTest.groovy:31)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:280)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1007)
	at groovy.lang.Closure.call(Closure.java:433)
	at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:52)
	at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:113)
	at jdk.proxy1/jdk.proxy1.$Proxy18.accept(Unknown Source)
	at org.assertj.core.api.SoftAssertionsProvider.assertSoftly(SoftAssertionsProvider.java:119)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.DomainSoftAssertions.assertSoftly(DomainSoftAssertions.groovy:11)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest$_test_closure1.doCall(MyTest.groovy:30)
	at org.example.custom.assertions.tests.MyTest$_test_closure1.doCall(MyTest.groovy)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:280)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1007)
	at groovy.lang.Closure.call(Closure.java:433)
	at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:52)
	at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:113)
	at jdk.proxy2/jdk.proxy2.$Proxy15.call(Unknown Source)
	at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:63)
	at org.assertj.core.api.NotThrownAssert.isThrownBy(NotThrownAssert.java:43)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest.test(MyTest.groovy:29)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:775)
	at org.junit.platform.commons.support.ReflectionSupport.invokeMethod(ReflectionSupport.java:479)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:161)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:152)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:91)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:112)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:94)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:87)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:216)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:212)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156)
	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:146)
	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:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	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:160)
	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:146)
	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:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	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:160)
	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:146)
	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:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	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:201)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:170)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:94)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:59)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:142)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:58)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85)
	at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
	at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$1(InterceptingLauncher.java:39)
	at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
	at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:38)
	at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
"

	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at org.example.custom.assertions.tests.MyTest.test(MyTest.groovy:29)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

scordio avatar Mar 14 '25 23:03 scordio

Could you advise on any workaround or point out where I might be handling the custom assertion implementation incorrectly?

@AlexeyAkentyev So far, I think the issue happens only with soft assertions. I couldn't pinpoint the root cause, though.

As a workaround, you might consider using satisfies in your test code.

scordio avatar Mar 15 '25 00:03 scordio

@scordio thanks a lot for the additional investigation details. I appreciate it. Based on your input, I've tried to figure out what conditions lead to the error.

I've modified the ForProxy method signature, and it works perfectly fine for SoftAssertion:

protected SELF containsExactlyInAnyOrderForProxy(List<ELEMENT> values) {
    iterables.assertContainsExactlyInAnyOrder(info, actual, toArray(values));
    return myself;
  }

Your test now passes without any issues:

package org.example.custom.assertions.tests

import org.example.custom.assertions.DomainSoftAssertions
import org.example.custom.assertions.domain.DomainObject
import org.junit.jupiter.api.Test

import static org.assertj.core.api.BDDAssertions.thenNoException

class MyTest {

  @Test
  void test() {
    // GIVEN
    def actualDomainObjects = [new DomainObject('actual', 1),
                               new DomainObject('actual', 2)]
    // WHEN/THEN
    thenNoException().isThrownBy {
      DomainSoftAssertions.assertSoftly { softly ->
        softly.assertThatDomainObjects(actualDomainObjects)
          .hasExpectedDomainObjects()
          .containsExactlyInAnyOrderElementsOf(actualDomainObjects)
      }
    }
  }

}

It looks like the issue is related to arrays. I've reproduced it with other ForProxy methods that use vararg/array parameters as well.

I hope this helps with the root cause analysis and fix.

AlexeyAkentyev avatar Mar 17 '25 19:03 AlexeyAkentyev

Thanks for following up on this!

Would you like to create a PR with the fix?

scordio avatar Mar 17 '25 19:03 scordio

This leads to significant internal changes. As a solution, it will require switching from Array to List structures for any ForProxy method parameters.

Does this sound like an applicable solution?

If yes, I can try it out and share the results in a PR.

AlexeyAkentyev avatar Mar 17 '25 19:03 AlexeyAkentyev

As a solution, it will require switching from Array to List structures for any ForProxy method parameters.

I don't see it as a problem 👍

We are already working on the next major version and breaking changes can definitely happen, especially to fix such topics.

So yes, a PR would be very welcome! 🙂

BTW, I haven't played enough with the issue to see if the same can be reproduced also in plain Java and not in Groovy. If plain Java is an option, I would favor it for the new test cases.

Thanks in advance and let us know if you need any support!

scordio avatar Mar 17 '25 21:03 scordio