Upgrade to Pro — share decks privately, control downloads, hide ads and more …

State of JUnit 5 in Android

State of JUnit 5 in Android

Aung Kyaw Paing

November 16, 2024
Tweet

More Decks by Aung Kyaw Paing

Other Decks in Technology

Transcript

  1. Aung Kyaw Paing Senior Consultant @ thoughtworks | GDE Android

    aungkyawpaing.dev State of Junit 5 in Android
  2. “JUnit was born on a flight from Zurich to the

    1997 OOPSLA in Atlanta. Kent was flying with Erich Gamma, and what else were two geeks to do on a long flight but program?” - Martin Fowler
  3. “Never in the field of software development have so many

    owed so much to so few lines of code” - Martin Fowler
  4. Junit 4 - Annotations were introduced - Method name no

    longer require you to start with “test” prefix - Runners are introduced
  5. Runners - Can only have one single runner for each

    test class - You can’t have Parameterized test if you’re using other class runner like AndroidTestRunner - We want to separate runner, reporting and so on into different interfaces
  6. Execution model - Requires all test to be known prior

    to execution - Prevent dynamic creation of test cases during execution
  7. Toolchain - IDEs and build tools are tightly coupled to

    JUnit internals - Some tools use reflection to access internal APIs - In short, JUnit 4 was not made to be extensible across multiple tools
  8. The vision - Decouple test execution and reporting from test

    definition and provisioning - Rethinking JUnit’s extensibility - Make use of Java 8 features
  9. Multiple Assertions Assertions.assertAll( "Assertions subset of variable", { Assertions.assertEquals(actual.name, "Vincent")},

    { Assertions.assertEquals(actual.age, 28)}, { Assertions.assertTrue(actual.isSpeaker)}, )
  10. Assumptions @Test fun shouldGetFromDatabase() { // Abort test if watch

    is not connected Assumptions.assumeTrue(isConnectedToWearOS())) // Execute test }
  11. Inner Tests class ProfileScreenTest { @InnerTest @DisplayName("when user is logged

    in") inner class WhenUserLoggedInTests { // ... test functions } @InnerTest @DisplayName("when user is logged out") inner class WhenUserLoggedOutTests { // ... test functions } }
  12. Parameterized tests @ParameterizedTest @ValueSource( strings = ["", "abcd", "abc213"] )

    fun validateShouldReturnFalse(input: String) { Assertions.assertFalse(validatePhoneNumber(input)) }
  13. Dynamic Tests @TestFactory fun apiContractTests(): List<DynamicTest> { val apiDataFile :

    List<String> = readAssetDir("api_tests") apiDataFile.map { metadata -> dynamicTest("API Test: ${metadata.endpoint}") { // PING API } } }
  14. class JUnit4Test { @Before fun setUp() { } @After fun

    tearDown() {} @Test fun test() { Assert.assertEquals(true, true) } } Similar structures class JUnit5Test { @BeforeEach fun setUp() { } @AfterEach fun tearDown() {} @Test fun test() { Assertions.assertEquals(true, true) } }
  15. JUnit 5 on Android - Google has no plan to

    officially support for Junit 5 as it will require a lot of resources
  16. JUnit 5 on Android - Google has no plan to

    officially support for Junit 5 as it will require a lot of resources - However we have community maintained plugin!
  17. plugins { id("de.mannodermaus.android-junit5") version "1.11.2.0" } dependencies { // (Required)

    Writing and executing Unit Tests on the JUnit Platform testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.2") // (Optional) If you need "Parameterized Tests" testImplementation("org.junit.jupiter:junit-jupiter-params:5.11.2") // (Optional) If you also have JUnit 4-based tests testImplementation("junit:junit:4.13.2") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.11.2") }
  18. Instrumentation Tests class MyActivityTest { @JvmField @RegisterExtension val scenarioExtension =

    ActivityScenarioExtension.launch<MyActivity>() @Test fun myTest() { val scenario = scenarioExtension.scenario // Do something with the scenario here... } }
  19. Compose Tests class ComposeTest { @JvmField @RegisterExtension val extension =

    createComposeExtension() @Test fun composeTest() = extension.use { setContent { Text("Hello") } onNodeWithText("Hello").assertIsDisplayed() } }
  20. Roboletric Android JUnit4 - No official support yet (#3477) -

    Is part of Google Summer of Code 2024 (Pitch Deck)
  21. Roboletric Android JUnit4 - No official support yet (#3477) -

    Is part of Google Summer of Code 2024 (Pitch Deck) - But we have a community plugin!
  22. Gradle Managed Device Gradle Managed Device is the recommended way

    to run instrumentation tests as it interacts with actual system API and it’s fast unlike running on actual emulators or real devices. It is also scalable with Firebase Test Lab!
  23. testOptions { managedDevices { localDevices { create("pixel") { // Use

    device profiles you typically see in Android Studio. device = "Pixel 2" // Use only API levels 27 and higher. apiLevel = 30 // To include Google services, use "google". systemImageSource = "aosp" } } } }
  24. Unit Tests Features Supported? Instrumentation Compose Dagger Hilt HiltAndroidRule has

    no equivalent extension as it requires annotating with HiltAndroidTest to generate code Robolectric Limited support
  25. My Recommendations Unit Tests Instrumentation Tests UI Tests E2E Junit

    5 Junit 5 running on Gradle Managed Device Maestro, Appium etc
  26. What can I do? - +1 in the issue tracker

    and voice out if you have any https://issuetracker.google.com/issues/127100532 - Thumbs up on Robolectric open issue https://github.com/robolectric/robolectric/issues/3477 - Star android-junit5 library https://github.com/mannodermaus/android-junit5 - Start using Junit5 in your app
  27. Aung Kyaw Paing Senior Consultant @ thoughtworks | GDE Android

    aungkyawpaing.dev State of Junit 5 in Android