Aung Kyaw Paing
Senior Consultant @ thoughtworks | GDE Android
aungkyawpaing.dev
State of Junit 5 in
Android
Slide 2
Slide 2 text
1997
Slide 3
Slide 3 text
“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
Slide 4
Slide 4 text
In fact, the original SUnit framework only has only
three small classes and twelve methods!
Slide 5
Slide 5 text
“Never in the field of software
development have so many
owed so much to so few lines of
code”
- Martin Fowler
Slide 6
Slide 6 text
2006
Slide 7
Slide 7 text
Junit 4
- Annotations were introduced
- Method name no longer require you to start with “test” prefix
- Runners are introduced
Slide 8
Slide 8 text
2016
Slide 9
Slide 9 text
No content
Slide 10
Slide 10 text
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
Slide 11
Slide 11 text
Execution model
- Requires all test to be known prior to execution
- Prevent dynamic creation of test cases during execution
Slide 12
Slide 12 text
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
Slide 13
Slide 13 text
The vision
- Decouple test execution and reporting from test definition and provisioning
- Rethinking JUnit’s extensibility
- Make use of Java 8 features
Slide 14
Slide 14 text
2017
JUnit 5 become stable
Slide 15
Slide 15 text
What’s in JUnit 5
for me?
Slide 16
Slide 16 text
Cleaner Reporting
Slide 17
Slide 17 text
Kotlin Friendly
@Test
fun `should return invalid when text is empty`() {
...
}
Assumptions
@Test
fun shouldGetFromDatabase() {
// Abort test if watch is not connected
Assumptions.assumeTrue(isConnectedToWearOS()))
// Execute test
}
Slide 21
Slide 21 text
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
}
}
Slide 22
Slide 22 text
Tagging
@Test
@Tag("Fast")
fun fastTestThatCanRunLocally()
@Test
@Tag("Slow")
fun slowTestThatWeRunOnCI()
Dynamic Tests
@TestFactory
fun apiContractTests(): List {
val apiDataFile : List = readAssetDir("api_tests")
apiDataFile.map { metadata ->
dynamicTest("API Test: ${metadata.endpoint}") {
// PING API
}
}
}
Slide 28
Slide 28 text
JUnit 4 Backwards
compatibility
Slide 29
Slide 29 text
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)
}
}
Slide 30
Slide 30 text
@get:Rule
val composeTestRule = createComposeRule()
Rule —> Extensions
Slide 31
Slide 31 text
Rule —> Extensions
@JvmField
@RegisterExtension
val extension = createComposeExtension()
Slide 32
Slide 32 text
implementation
("org.junit.vintage:junit-vintage-engine")
Or just run Junit4!
Slide 33
Slide 33 text
JUnit 5 on Android
Slide 34
Slide 34 text
JUnit 5 on Android
- Google has no plan to officially support for Junit 5 as it will require a lot of resources
Slide 35
Slide 35 text
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!
Slide 36
Slide 36 text
plugins {
id("de.mannodermaus.android-junit5") version "1.11.2.0"
}
Slide 37
Slide 37 text
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")
}
Instrumentation Tests
class MyActivityTest {
@JvmField
@RegisterExtension
val scenarioExtension = ActivityScenarioExtension.launch()
@Test
fun myTest() {
val scenario = scenarioExtension.scenario
// Do something with the scenario here...
}
}
Compose Tests
class ComposeTest {
@JvmField
@RegisterExtension
val extension = createComposeExtension()
@Test
fun composeTest() = extension.use {
setContent {
Text("Hello")
}
onNodeWithText("Hello").assertIsDisplayed()
}
}
Slide 42
Slide 42 text
Roboletric Android JUnit4
- No official support yet (#3477)
- Is part of Google Summer of Code 2024 (Pitch Deck)
Slide 43
Slide 43 text
Roboletric Android JUnit4
- No official support yet (#3477)
- Is part of Google Summer of Code 2024 (Pitch Deck)
- But we have a community plugin!
Slide 44
Slide 44 text
Roboletric
plugins {
id("tech.apter.junit5.jupiter.robolectric-extension-gradle-plugin") version "0.8.0"
}
@ExtendWith(RobolectricExtension::class)
class RobolectricTest {
@Test
fun `JUnit5 with robolectric`() {
assertNotNull(RuntimeEnvironment.getApplication())
}
}
Slide 45
Slide 45 text
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!
Slide 46
Slide 46 text
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"
}
}
}
}
Slide 47
Slide 47 text
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
Slide 48
Slide 48 text
My Recommendations
Unit Tests
Instrumentation Tests
UI Tests
E2E
Junit 5
Junit 5 running on
Gradle Managed
Device
Maestro, Appium
etc
Slide 49
Slide 49 text
So what can you do?
Slide 50
Slide 50 text
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
Slide 51
Slide 51 text
Aung Kyaw Paing
Senior Consultant @ thoughtworks | GDE Android
aungkyawpaing.dev
State of Junit 5 in
Android