David Corrado
10/31/2024, 4:00 PMDavid Corrado
10/31/2024, 4:01 PMDavid Corrado
10/31/2024, 4:01 PM@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.
"Gustavo Barbosa Barreto
11/15/2024, 1:42 PMGustavo Barbosa Barreto
11/15/2024, 1:51 PMDavid Corrado
11/15/2024, 3:54 PMGustavo Barbosa Barreto
11/15/2024, 3:57 PMGustavo Barbosa Barreto
11/15/2024, 3:59 PMDavid Corrado
11/15/2024, 4:13 PM@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) }
}
Gustavo Barbosa Barreto
11/15/2024, 4:42 PMGustavo Barbosa Barreto
11/15/2024, 4:43 PM