data ◦ movieDatabse.insert(movie) • Activity class - contains all the views and view logic ◦ private lateinit var titleEditText: EditText ◦ titleEditText.setText(data?.getStringExtra(EXTRA_TITLE)) • The brain that controls what the Model and View does ◦ model.insert(movie) ◦ view.displayError("Movie title cannot be empty") Model Presenter View
: AddMovieContract.ViewInterface @Mock private val mockModel : LocalDataSource private lateinit var presenter : AddMoviePresenter //Add tests here } Makes it easy to use the @Mock annotation to create mocks
◦ open class LocalDataSource(application: Application) { private val movieDao: MovieDao open fun insert(movie: Movie) { thread { movieDao.insert(movie) } } }
◦ open class LocalDataSource(application: Application) { private val movieDao: MovieDao open fun insert(movie: Movie) { thread { movieDao.insert(movie) } } } 2. Use interfaces ◦ Comes free with MVP! Interfaces in Kotlin are NOT final!
◦ open class LocalDataSource(application: Application) { private val movieDao: MovieDao open fun insert(movie: Movie) { thread { movieDao.insert(movie) } } } 2. Use interfaces ◦ Comes free with MVP! 3. Use a library: mockK, mockito-kotlin, Mockito 2
◦ testImplementation 'org.mockito:mockito-core:2.2.5' 2. Create a new file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 3. Add this single line to the file: ◦ mock-maker-inline
often return null From docs: By default, for all methods that return value, mock returns null, an empty collection or appropriate primitive/primitive wrapper value (e.g: 0, false, ... for int/Integer, boolean/Boolean, ...) In Kotlin, this results in NPEs at runtime: java.lang.IllegalStateException: movieArgumentCaptor.capture() must not be null
often return null From docs: By default, for all methods that return value, mock returns null, an empty collection or appropriate primitive/primitive wrapper value (e.g: 0, false, ... for int/Integer, boolean/Boolean, ...) Mockito.verify(mock).someMethod(anyInt(), any(), eq("my third argument")); returns 0 returns null returns null
<T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() is the same as fun <T> capture(argumentCaptor: ArgumentCaptor<T: Any?>): T = argumentCaptor.capture() Return type is also Any?
FROM movie_table") val allMovies: Observable<List<Movie>> Unit Test: @Test fun testGetMyMoviesList() { `when`(mockModel.allMovies).thenReturn(Observable.just(myDummyMovies)) }
src/main, but instead used src/ as our main source directory Gradle wasn’t able to identify where the tests were • Could only run either kotlin or java tests sourceSets { main.kotlin.srcDirs = ['src/main/kotlin', 'src/main/java'] main.java.srcDirs = [] test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] Test.java.srcDirs = ['src/test/kotlin', 'src/test/java'] }
backticks will allow you to write tests with spaces in them @Test fun `test getReleaseYear from string date`() { // } @Test fun `test getReleaseYear from string year`() { // } @Test fun `test getReleaseYear from empty date`() { // } @Test fun `test getReleaseYear from null date`() { // }
annotation to group test methods (requires JUnit 5) @Nested inner class TypicalTests { @Test fun `test getReleaseYear from string formatted date`() { // } @Test fun `test getReleaseYear from year`() { // } } @Nested inner class EdgeCaseTests { @Test fun `test getReleaseYear from empty date`() { // } @Test fun `test getReleaseYear from null date`() { // } }
lateinit • Kotlin classes are final by default ◦ open keyword, interfaces, libraries • Use helper functions for Mockito Matchers and ArgumentCaptors ◦ MockitoKotlinHelpers.kt • Use backticks for `when` • Use the default Android Studio directory structure • Use backticks around test names if you want to include spaces • Group tests together using @nested (requires JUnit 5)