Testing your app is an integral part of app development. By running tests against your app consistently, you can verify your app’s correctness, functional behavior, and usability before you release it publicly.
Testing also provides you with the following advantages:
- Rapid feedback on failures.
- Early failure detection in the development cycle.
- Safer code refactoring, letting you optimize code without worrying about regressions.
- Stable development velocity, helping you minimize technical debt
In this blog we are going to see the fundamentals of testing, different types of testing and frameworks which make the testing easier.
Level of testing
- Small test – are unit test which validates the app’s behaviour one class at a time
- Medium test ( integrated testing ) – are integration tests that validate either interactions between levels of the stack within a module, or interactions between related modules.
- Large tests – are end-to-end tests that validate user journeys spanning multiple modules of your app.
Types of testing
- Local unit testing
- Instrumented unit testing
Local unit testing
- Run in our local machine
- Functions that are not dependent on any android api can be done in the local unit testing without a real device.
In some cases we may need to access the android api, to achieve this we can use other frameworks like Robolectric, Espresso and Mockito.
Instrumented testing
These are unit tests which rely on interaction with the view and need real devices or emulators to run, which can have access to instrumented information like context of the app.
It’s best to rely on this method only when it’s essential to evaluate your app’s behavior against actual device hardware.
In the app java folders you can see androidTest and test package.
Test package is where you can write your unit test cases. The test cases in this folder will be running in our local machine
AndroidTest package is the folder where you add all the instrumented test cases, which requires real device or emulator to run.
Dependencies
// for unit testing
testImplementation 'junit:junit:4.13.2'
// For instrumented testing
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
Unit Test
Create a test class for the class you need to write test cases. Then create a function and annotate it with @Test to make the function testable.
User Assert to validate whether you test cases is passed or failed.
class EmailValidatorTest {
@Test
fun validEmailTestPassed() {
val email = "user@mail.com"
Assert.assertTrue(EmailValidator.isValidEmail(email))
}
}
To run the test case. You can click the green arrow icon on the left side of the class. And select run. Or you can right click inside the class or the file name and select run. You can also run individual test cases.
Instrumented test
Next let’s see how we can write instrumented test cases
Similar to unit test case , create a class in androidTest package.
Annotate the class with @RunWith(AndroidJUnit4::class)
AndroidJUnitRunner is the instrumentation runner. This is essentially the entry point into running your entire suite of tests. It controls the test environment, the test apk, and launches all of the tests defined in your test package. You configure this in your gradle file
android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
AndroidJUnit4 is the class test runner. This is the thing that will drive the tests for a single class. You annotate your test classes with this:
Next we need to start an activity to start the testing. ActivityScenarioRule launches a given activity before the test starts and closes after the test.
@Before is used to execute a function each time before a test function starts and @After is used to execute a function after a test function is completed.
import android.os.Parcel
import android.text.TextUtils.writeToParcel
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
const val TEST_STRING = "This is a string"
const val TEST_LONG = 12345678L
// @RunWith is required only if you use a mix of JUnit3 and JUnit4.
@RunWith(AndroidJUnit4::class)
@SmallTest
class LogHistoryAndroidUnitTest {
private lateinit var logHistory: LogHistory
@Before
fun createLogHistory() {
logHistory = LogHistory()
}
@Test
fun logHistory_ParcelableWriteRead() {
val parcel = Parcel.obtain()
logHistory.apply {
// Set up the Parcelable object to send and receive.
addEntry(TEST_STRING, TEST_LONG)
// Write the data.
writeToParcel(parcel, describeContents())
}
// After you're done with writing, you need to reset the parcel for reading.
parcel.setDataPosition(0)
// Read the data.
val createdFromParcel: LogHistory = LogHistory.CREATOR.createFromParcel(parcel)
createdFromParcel.getData().also { createdFromParcelData: List<Pair<String, Long>> ->
// Verify that the received data is correct.
assertThat(createdFromParcelData.size).isEqualTo(1)
assertThat(createdFromParcelData[0].first).isEqualTo(TEST_STRING)
assertThat(createdFromParcelData[0].second).isEqualTo(TEST_LONG)
}
}
}
To perform UI actions and testing we can use Espresso library.
Espresso
Espresso framework provides a way to access the view and perform actions in that view. It helps in keeping the tasks synchronized as we do in real devices.
The main components of Espresso include the following:
- Espresso – Entry points to interactions with views (via onView() and onData()). Also exposes APIs that are not necessarily tied to any view, such as pressBack().
- ViewMatchers – A collection of objects that implement the Matcher<? super View> interface. You can pass one or more of these to the onView() method to locate a view within the current view hierarchy.
- ViewActions – A collection of ViewAction objects that can be passed to the ViewInteraction.perform() method, such as click().
- ViewAssertions – A collection of ViewAssertion objects that can be passed the ViewInteraction.check() method. Most of the time, you will use the matches assertion, which uses a View matcher to assert the state of the currently selected view.
Test suite :
To organize the execution of your instrumented unit tests, you can group a collection of test classes in a test suite class and run these tests together. Test suites can be nested, your test suite can group other test suites and run all their component test classes together.
@RunWith(Suite::class)
@Suite.SuiteClasses(
ProductActivityTest::class,
ProductDetailFragmentTest::class,
ProductListFragmentTest::class
)
class ProductSuite
All you need to do is create a suit class and annotate it with @RunWith(Suite::class) and pass the instrumented test classes inside the @Suite.SuiteClasses() annotation.
To see the real time implementation of testing with hilt checkout the feature blogs Login testing and Product testing
Some of the libraries which can be used while testing are Robolectric and Mockito.
Robolectric Framework
Robolectric is an unit testing framework that simulates the working behaviour of a real device or emulator.
Pros :
- Robolectric runs the unit test in our workstation or in a continuous integration environment in a regular JVM without an emulator.
- Bcz of this dexing , packaging and installing on emulator steps aren’t necessary, making the cycles of testing very fast.
- It handles the inflation of view, loading the resources and other stuff done in the real device.
Cons :
- Device behaviour like sensor and other hardware access is not possible since it requires a real device.
Mockito
Mockito is an android testing framework that provides a way to mock data objects. This can be useful in cases where the unit test case relies on data from a local db or api.
Mockito provides much apis to use – https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
That’s it, I hope this blog gave you some ideas on writing test cases to your application. This is just a basic introduction on testing. To learn more about testing check out the documentation – https://developer.android.com/training/testing
Happy learning
Team Appmetry