Compose Testing Advanced 03: Parameterized Tests 🔄
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.
- 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 takesusername
,password
, andisLoginEnabled
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
- Use Descriptive Names: Ensure that the parameter names and test names are descriptive for better readability.
- Isolate Test Logic: Keep the test logic isolated from other tests to avoid interdependencies.
- Handle Edge Cases: Include edge cases in your parameterized tests to ensure comprehensive coverage.
- 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! 🚀