Compose Testing Advanced 03: Parameterized Tests 🔄

EspressoLab.Ai
3 min readJun 19, 2024

--

Introduction

Welcome to the third installment of our advanced series on Compose Testing! In this post, we’ll explore parameterized tests in Jetpack Compose using a real-world use case. Parameterized tests allow you to run the same test logic with different input parameters, making your tests more robust and reducing code duplication. This guide will provide detailed examples and best practices for creating and using parameterized tests in Compose.

Why Use Parameterized Tests?

Parameterized tests are beneficial because they:

  • Enhance Test Coverage: Test multiple scenarios with different inputs.
  • Reduce Code Duplication: Reuse the same test logic for different data sets.
  • Improve Test Maintainability: Easier to update and maintain a single test logic.

Use Case: Form Validation

Let’s consider a real-world use case: testing form validation in a login screen. Our login form has two fields, “Username” and “Password”, and a “Login” button. We want to verify that the login form behaves correctly with various inputs, including valid and invalid credentials.

Setting Up the Form UI

First, we’ll define our form UI in Compose:

@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var isLoginEnabled by remember { mutableStateOf(false) }

Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
TextField(
value = username,
onValueChange = {
username = it
isLoginEnabled = username.isNotEmpty() && password.isNotEmpty()
},
label = { Text("Username") },
modifier = Modifier.testTag("usernameField")
)
TextField(
value = password,
onValueChange = {
password = it
isLoginEnabled = username.isNotEmpty() && password.isNotEmpty()
},
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.testTag("passwordField")
)
Button(
onClick = { onLogin(username, password) },
enabled = isLoginEnabled,
modifier = Modifier.testTag("loginButton")
) {
Text("Login")
}
}
}

Creating Parameterized Tests

We’ll create a parameterized test class to verify the behavior of the login form with different sets of credentials.

  1. Define the Test Class:
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performClick
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Parameterized::class)
class LoginFormParameterizedTest(
private val username: String,
private val password: String,
private val isLoginEnabled: Boolean
) {

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testLoginForm() {
var loginAttempted = false
composeTestRule.setContent {
LoginForm { _, _ ->
loginAttempted = true
}
}

composeTestRule.onNodeWithTag("usernameField").performTextInput(username)
composeTestRule.onNodeWithTag("passwordField").performTextInput(password)

val loginButton = composeTestRule.onNodeWithTag("loginButton")
if (isLoginEnabled) {
loginButton.assertIsDisplayed().performClick()
assert(loginAttempted)
} else {
loginButton.assertIsDisplayed().assertDoesNotHaveClickAction()
}
}

companion object {
@JvmStatic
@Parameterized.Parameters(name = "{index}: username={0}, password={1}, isLoginEnabled={2}")
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf("user1", "password1", true),
arrayOf("user2", "", false),
arrayOf("", "password2", false),
arrayOf("user3", "password3", true)
)
}
}
}

In this example:

  • We define a LoginFormParameterizedTest class that takes username, password, and isLoginEnabled as parameters.
  • We use the Parameterized runner to define the data set for the test.
  • The testLoginForm function inputs the username and password into the form, then checks if the "Login" button is enabled and behaves correctly.

Running the Tests

When you run the parameterized tests, JUnit will execute the testLoginForm method with each set of parameters defined in the data method. This approach ensures that your login form is thoroughly tested with various inputs, covering multiple scenarios in a single test class.

Best Practices for Parameterized Tests

  1. Use Descriptive Names: Ensure that the parameter names and test names are descriptive for better readability.
  2. Isolate Test Logic: Keep the test logic isolated from other tests to avoid interdependencies.
  3. Handle Edge Cases: Include edge cases in your parameterized tests to ensure comprehensive coverage.
  4. Reuse Test Components: Reuse composable functions and components to reduce redundancy in your tests.

Follow Us for More Insights 🌟

Stay updated with the latest tips and best practices for QA engineers in Android UI testing by following us on Medium. For more resources and tools to enhance your testing skills, visit our website EspressoLab.ai.

Follow us on Medium: EspressoLab Medium

Thank you for reading! 🚀

--

--

EspressoLab.Ai

Empowering Software Engineers with top-notch digital products and online courses.