Got an interesting issue. Apparently roboelectric...
# mockk
d
Got an interesting issue. Apparently roboelectric and mockk do not play well together. Is there any known way to make them work better together. See 🧵
https://github.com/DavidCorrado/MockingUnitTestFailure/blob/main/app/src/test/java/com/example/mocktest/ExampleUnitTest.kt If you move the native annotation to the class the tests succeed. If you run the tests individually they work
Chat GPT response to this "Certainly! The error you're seeing when using
@GraphicsMode(GraphicsMode.Mode.NATIVE)
in Robolectric with MockK stems from how Robolectric and MockK each manage class loading, particularly when handling proxies in combination with native graphics rendering. Let’s break down the main factors involved: 1. Class Proxying and Mocking Mechanism in MockK • MockK uses a proxy-based approach for mocking. It relies on
ByteBuddy
to generate subclasses and interceptors for mocks, especially for interfaces and lambda functions (e.g.,
Function0
in Kotlin). • When MockK tries to create a proxy for a lambda, it generates a subclass that mimics the original class or interface (
Function0
in this case). This subclassing involves loading a new class into memory, often using a specialized class loader. 2. Robolectric's Graphics Mode (Native) and Class Loading • Robolectric, when running in
GraphicsMode.NATIVE
, uses native OpenGL support to render the graphics, which enables more accurate graphics behavior compared to its default “simulated” graphics mode. • However,
GraphicsMode.NATIVE
also adds some restrictions on the class loading mechanism to avoid loading certain classes that could conflict with its rendering process. 3. Class Loading Conflict and
LinkageError
• When Robolectric is in
GraphicsMode.NATIVE
, it uses a more restrictive class loader environment that doesn’t always play nicely with MockK’s class proxy generation. • The
LinkageError
you see (
loader 'app' attempted duplicate class definition for kotlin.jvm.functions.Function0$Subclass0
) happens because Robolectric’s restrictive native graphics mode conflicts with MockK’s attempt to dynamically subclass
Function0
via bytecode injection. • Specifically, Robolectric prevents reloading or redefining classes that already exist in its context. The
Function0
interface gets loaded once by Robolectric, but MockK tries to subclass it again, resulting in an error because the restrictive class loader detects this as a duplicate. 4. Why This Doesn't Happen When Moving
@GraphicsMode
to the Class Level
• Moving
@GraphicsMode(GraphicsMode.Mode.NATIVE)
to the class level allows Robolectric to set up the entire class (including mocks and dependencies) in native graphics mode right from the beginning. • When
@GraphicsMode
is set at the method level, Robolectric dynamically switches the graphics mode within an already-loaded class context, leading to conflicts with any existing proxies created by MockK at the class setup stage. "
g
Hey David, I'm facing an issue almost like yours. I'm also using Mockk and Robolectric, but in my case I'm trying to test if a composable is invoking its listeners properly. I don't know why, but Robolectric is not creating the composables in a lazy row, so when I try to verify if a mocked function was called it breaks. It's weird because if I try to assert if the composable exists by using espresso it works properly and the test pass, but using Mockk's verify method it fails. 😕
Is there anyone facing or that faced the same?
d
I am not sure our issue is the same. As if you run just the 1 unit test alone mine works. I do not think yours would work based on your description
g
I don’t think they are the same, but related to the same two libs.
Robolectric + Compose Test work fine during the assert. But it doesn’t work when I run Robolectric + Mockk verify method.
d
Here is an example of using roboelectric + mockk + verify. Maybe it will help?
Copy code
@Test
fun `FeedbackBottomSheet triggers onRateChanged when user enters rating`() {
    val onRateChanged = mockk<(rating: Float) -> Unit>(relaxed = true)
    rule.setContent {
        FeedbackBottomSheet(
            prompt = "Benefit",
            onSubmitFeedback = mockk(relaxed = true),
            sheetState = rememberModalBottomSheetState(),
            message = "test",
            rating = 1f,
            onMessageChanged = mockk(),
            onRateChanged = onRateChanged,
            isSendFeedbackButtonEnabled = true
        )
    }
    rule.onNodeWithTag(WELL_RATING_BAR_TEST_TAG).assertExists().performClick()
    verify { onRateChanged.invoke(3f) }
}
Btw, the same test pass when running as an instrumented test.