Android App From Scratch Part 4— Creating Unit Tests with JUnit
In this tutorial series, I will try to create an RSS Reader app step by step. Through this series I will explain:
- How to use Model-View-Presenter in an Android App
- Implementing must have libraries
- Implementing App Logic
- Creating unit tests with JUnit
- Creating Android Instrumentations tests with Espresso
- Continuous Integration with Travis-CI
In this part I will create some unit tests. I mentioned that MVP is a good approach for unit testing. All logic is kept in presenters. Testing the presenter will cover almost the whole logic.
I used mockito-kotlin to mock objects.
testImplementation "com.nhaarman:mockito-kotlin:1.6.0"
MainPresenterTest.kt
MainPresenter
class doesn’t have too much logic. It has only a method to create the ViewPager
. So the test will only check that if MainContract.View
method(s) are called or not.
At firs I mocked the data layer for the presenter. I created MockMainRepository
that returns mock data.
class MockMainRepository : MainRepository {
override fun parseFeeds(): List<Feed> {
return MockData.FEEDS
}
}
Then inject repository into the Presenter.
class MainPresenterTest {
private lateinit var mainPresenter: MainContract.Presenter
private val view: MainContract.View = mock()
@Before
fun setup() {
val repository = MockMainRepository()
mainPresenter = MainPresenter(repository)
mainPresenter.attach(view)
}
@Test
fun testLoadItems() {
mainPresenter.loadRssFragments()
argumentCaptor<List<Feed>>().apply {
verify(view).onLoadRssFragments(capture())
Assert.assertEquals(firstValue, MockData.FEEDS)
}
}
}
setup()
method runs before test methods. Initialisations done here.
mock()
method creates a mock object. MainContract.View
is mocked here.
In testLoadItems()
method, presenter method loadRssFragments()
is called then verifies that View callback is called or not with argumentCaptor()
. capture()
method captures the parameter passed to the method. firstValue
is the value of captured object. With assertEquals
method it is verified that the value is correct or not.
Now let’s run the test and see the result:
RssPresenterTest.kt
class RssPresenterTest {
private lateinit var presenter: RssContract.Presenter
private val cache = RssCache()
private val view: RssContract.View = mock()
private var repository = MockRssRepository(true)
@Before
fun setup() {
presenter = RssPresenter(repository, cache)
presenter.attach(view)
}
@Test
fun testLoadRssItems() {
presenter.loadRssItems(MockData.FEEDS[0], false)
argumentCaptor<List<RssItem>>().apply {
verify(view).showLoading()
verify(view).onRssItemsLoaded(capture())
Assert.assertTrue(firstValue == MockData.ITEMS)
verify(view).hideLoading()
}
}
@Test
fun testLoadRssItemsFromCache() {
cache.addContent(MockData.FEEDS[0].url, MockData.ITEMS)
presenter = RssPresenter(repository, cache)
presenter.attach(view)
presenter.loadRssItems(MockData.FEEDS[0], true)
argumentCaptor<List<RssItem>>().apply {
verify(view).onRssItemsLoaded(capture())
Assert.assertTrue(firstValue == MockData.ITEMS)
}
}
@Test
fun testFailRssItems() {
presenter = RssPresenter(MockRssRepository(false), cache)
presenter.attach(view)
presenter.loadRssItems(MockData.FEEDS[0], true)
argumentCaptor<Error>().apply {
verify(view).onFail(capture())
Assert.assertTrue(firstValue == Error.generic())
verify(view).hideLoading()
}
}
}
There are 3 test cases for RssPresenter
:
- Loading RSS Items by fetching service
- Loading RSS Items from cache
- Failing the RSS service
setup()
method generates mock RSS items then attaches the presenter.
argumentCaptor
methods are used to capture the method parameters of mocked View
callbacks.
testLoadRssItems()
is explained step by step:
- Presenter
onSuccess()
method is called with mocked repository data - Checks if
showLoading()
method is called because data has to be fetched from a network call - Checks if
onRssItemsLoaded()
method is called with a successful response - Checks if captured object equals the mock objects
- Checks if
hideLoading()
method is called after network call
testLoadRssItemsFromCache()
methods calls presenter method with fromCache
parameter enabled. Then checks if cached objects are returned.
testLoadFail()
method calls presenter onFail()
method with mock Url then checks if view’s onFail()
is called properly.
Creating a TestSuite
To organize the execution of your unit tests, multiple unit test classes can be grouped with a TestSuite.
@RunWith(Suite::class)
@Suite.SuiteClasses(MainPresenterTest::class, RssPresenterTest::class)
class UnitTestSuite
This test suite will run all unit tests.
To see the test outputs from command line, Add the task below to app level gradle(not in android { … } part) :
tasks.withType(Test) {
testLogging {
events "started", "passed", "skipped", "failed"
}
}
Here is the full source code for this part:
Continue reading with the next part?
If you liked the article, please 👏👏👏 so more people can see it! Also, you can follow me on Medium