Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot mock sealed class or sealed interface with AGP 8.1.0 #3086

Open
5 tasks done
Nynuzoud opened this issue Aug 9, 2023 · 5 comments
Open
5 tasks done

Cannot mock sealed class or sealed interface with AGP 8.1.0 #3086

Nynuzoud opened this issue Aug 9, 2023 · 5 comments

Comments

@Nynuzoud
Copy link

Nynuzoud commented Aug 9, 2023

Hello,

After update to AGP 8.1 and JKD 17 I cannot mock sealed classes or sealed interfaces. It works only if subclass is defined. But this is not a solution, since the project is huge and it leads to big number of changes. Also, it adds a confusion why certain subclass is mocked. So, it is better to have a mocked sealed class/interface instead.

Stacktrace:

Mockito cannot mock this class: class com.project.SomeItem.

If you're not sure why you're getting this error, please open an issue on GitHub.


Java               : 17
JVM vendor name    : Amazon.com Inc.
JVM vendor version : 17.0.8+7-LTS
JVM name           : OpenJDK 64-Bit Server VM
JVM version        : 17.0.8+7-LTS
JVM info           : mixed mode, sharing
OS name            : Mac OS X
OS version         : 13.5


You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.

Underlying exception : org.mockito.exceptions.base.MockitoException: Unsupported settings with this type 'com.project.SomeItem'
org.mockito.exceptions.base.MockitoException: 
Mockito cannot mock this class: class com.project.SomeItem.

If you're not sure why you're getting this error, please open an issue on GitHub.


Java               : 17
JVM vendor name    : Amazon.com Inc.
JVM vendor version : 17.0.8+7-LTS
JVM name           : OpenJDK 64-Bit Server VM
JVM version        : 17.0.8+7-LTS
JVM info           : mixed mode, sharing
OS name            : Mac OS X
OS version         : 13.5


You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.

Underlying exception : org.mockito.exceptions.base.MockitoException: Unsupported settings with this type 'com.project.SomeItem'
	at app//com.project.Test.<init>(Test.kt:31)
	at java.base@17.0.8/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base@17.0.8/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base@17.0.8/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base@17.0.8/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base@17.0.8/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at app//org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:250)
	at app//org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:260)
	at app//org.junit.runners.BlockJUnit4ClassRunner$2.runReflectiveCall(BlockJUnit4ClassRunner.java:309)
	at app//org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at app//org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:306)
	at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at app//org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at app//org.junit.runner.JUnitCore.run(JUnitCore.java:115)
	at app//org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:42)
	at app//org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
	at app//org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:72)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
	at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
	at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
	at java.base@17.0.8/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base@17.0.8/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base@17.0.8/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base@17.0.8/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 app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.mockito.exceptions.base.MockitoException: Unsupported settings with this type 'com.project.SomeItem'
	at app//net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:168)
	at app//net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:399)
	at app//net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:190)
	at app//net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:410)
	... 58 more


Example code:

import org.junit.Test
import org.mockito.kotlin.mock

internal sealed class SomeItem(data: String) {
    data class ItemMember(val data1: String) : SomeItem(data1)
}
class Test {

    @Test
    fun `mockSomeItemWorks`() {
        mock<SomeItem>()
    }
}

It happens whenever you mock SomeItem sealed class.

  • Mockito version is 5.4.0 (core, android);
  • mockito-inline: 5.2.0
  • JDK17
  • ASM 9.5
  • Tried with ByteBuddy and ByteBuddy Agent with versions: 1.12.9; 1.12.23; 1.14.5

As you can see, I tried to play around with different ByteBuddy versions. Also, same behavior applies to older Mockito versions, i.e 4.11.0

It seems like this issue is related: #2975

check that

  • The mockito message in the stacktrace have useful information, but it didn't help
  • The problematic code (if that's possible) is copied here;
    Note that some configuration are impossible to mock via Mockito
  • Provide versions (mockito / jdk / os / any other relevant information)
  • Provide a Short, Self Contained, Correct (Compilable), Example of the issue
    (same as any question on stackoverflow.com)
  • Read the contributing guide
@Nynuzoud
Copy link
Author

Nynuzoud commented Nov 29, 2023

Ok, I got a chance to dig into the code and found this function:

private <T> void checkSupportedCombination(
        boolean subclassingRequired, MockFeatures<T> features) {
    if (subclassingRequired
            && !features.mockedType.isArray()
            && !features.mockedType.isPrimitive()
            && (Modifier.isFinal(features.mockedType.getModifiers())
                    || TypeSupport.INSTANCE.isSealed(features.mockedType)
                    || features.interfaces.stream().anyMatch(TypeSupport.INSTANCE::isSealed))) {
        throw new MockitoException(
                "Unsupported settings with this type '" + features.mockedType.getName() + "'");
    }
}

That is explained here: #2392

I debugged Class.java a bit and found out that getPermittedSubclasses0 returns subclasses for Kotlin Sealed class even if it is declared as abstract class in decompiled java code.

I also understand that it is no go add an additional support in mockito-core or byte-buddy of Kotlin just because of this case.

I'll try to take a look what can be done on mockito-kotlin part since byte-buddy seems to be supporting Kotlin sealed classes. So, this checkSupportedCombination might be (didn't check) is the only blocker on the way to mock Kotlin sealed class.

But I still think that this should be possible to mock sealed class at least for Kotlin.

Leaving this issue opened for now in case if somebody wants to get answers or help contributing into this problem.

@antonicg
Copy link

Same issue here:

Java               : 17
JVM vendor name    : Amazon.com Inc.
JVM vendor version : 17.0.9+8-LTS
JVM name           : OpenJDK 64-Bit Server VM
JVM version        : 17.0.9+8-LTS
JVM info           : mixed mode, sharing
OS name            : Mac OS X
OS version         : 14.2.1

@HaliksaR
Copy link

HaliksaR commented Feb 3, 2024

Mockito cannot mock this class: interface

Java               : 21
JVM vendor name    : Amazon.com Inc.
JVM vendor version : 21.0.2+13-LTS
JVM name           : OpenJDK 64-Bit Server VM
JVM version        : 21.0.2+13-LTS
JVM info           : mixed mode, sharing
OS name            : Mac OS X
OS version         : 14.1.2

deps

'kotlinMockito'      : "org.mockito.kotlin:mockito-kotlin:5.2.1",
'mockitoJupiter'     : "org.mockito:mockito-junit-jupiter:5.10.0",

args

jvmArgs("-Dnet.bytebuddy.experimental=true")

@kenyee
Copy link

kenyee commented Feb 13, 2024

Another example w/ Java17 (worked fine w/ Java11):

sealed class PlayerData {
    data class EventEnd(
        val timer: Int,
        val requestId: String?,
        val uuid: String?,
    ) : PlayerData()
}

when attempting to do something like:
val mock: PlayerData = mock()

Using latest versions of Mockito 5.10.0 BOM on Android.

@varunajaygupta
Copy link

I am also facing the same issue. Is there any update on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants