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