Fix unit tests (#5947)

* move createLocale() method to companion object and add test dependency

* use mockk() from Mockk library for mocking sealed classes

* change method parameter to null-able String type

* add null check for accessing property from unit tests

* change method signature to match old method's signature

It fixes the NullPointerException when running ImageProcessingUnitTest

* Fix unresolved references and make properties public for unit tests

* fix tests in UploadRepositoryUnitTest by making return type null-able
This commit is contained in:
Rohit Verma 2024-11-23 05:05:34 +05:30 committed by GitHub
parent fe347c21fd
commit e070c5dbe8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 56 additions and 52 deletions

View file

@ -98,6 +98,7 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.6.0' testImplementation 'org.mockito:mockito-core:5.6.0'
testImplementation "org.powermock:powermock-module-junit4:2.0.9" testImplementation "org.powermock:powermock-module-junit4:2.0.9"
testImplementation "org.powermock:powermock-api-mockito2:2.0.9" testImplementation "org.powermock:powermock-api-mockito2:2.0.9"
testImplementation("io.mockk:mockk:1.13.5")
// Unit testing // Unit testing
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'

View file

@ -124,7 +124,9 @@ class CategoryClient
}.map { }.map {
it it
.filter { page -> .filter { page ->
!page.categoryInfo().isHidden // Null check is not redundant because some values could be null
// for mocks when running unit tests
page.categoryInfo()?.isHidden != true
}.map { }.map {
CategoryItem( CategoryItem(
it.title().replace(CATEGORY_PREFIX, ""), it.title().replace(CATEGORY_PREFIX, ""),

View file

@ -6,7 +6,6 @@ import android.content.ContentValues
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.os.RemoteException import android.os.RemoteException
import java.util.ArrayList
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import javax.inject.Provider import javax.inject.Provider
@ -163,9 +162,9 @@ class RecentLanguagesDao @Inject constructor(
COLUMN_CODE COLUMN_CODE
) )
private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME" const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
private const val CREATE_TABLE_STATEMENT = "CREATE TABLE $TABLE_NAME (" + const val CREATE_TABLE_STATEMENT = "CREATE TABLE $TABLE_NAME (" +
"$COLUMN_NAME STRING," + "$COLUMN_NAME STRING," +
"$COLUMN_CODE STRING PRIMARY KEY" + "$COLUMN_CODE STRING PRIMARY KEY" +
");" ");"

View file

@ -19,10 +19,10 @@ import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import timber.log.Timber
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import timber.log.Timber
/** /**
* The repository class for UploadActivity * The repository class for UploadActivity
@ -46,7 +46,7 @@ class UploadRepository @Inject constructor(
* *
* @return * @return
*/ */
fun buildContributions(): Observable<Contribution> { fun buildContributions(): Observable<Contribution>? {
return uploadModel.buildContributions() return uploadModel.buildContributions()
} }
@ -150,7 +150,7 @@ class UploadRepository @Inject constructor(
* *
* @return * @return
*/ */
fun getSelectedLicense(): String { fun getSelectedLicense(): String? {
return uploadModel.selectedLicense return uploadModel.selectedLicense
} }
@ -173,11 +173,11 @@ class UploadRepository @Inject constructor(
* @return * @return
*/ */
fun preProcessImage( fun preProcessImage(
uploadableFile: UploadableFile, uploadableFile: UploadableFile?,
place: Place?, place: Place?,
similarImageInterface: SimilarImageInterface, similarImageInterface: SimilarImageInterface?,
inAppPictureLocation: LatLng? inAppPictureLocation: LatLng?
): Observable<UploadItem> { ): Observable<UploadItem>? {
return uploadModel.preProcessImage( return uploadModel.preProcessImage(
uploadableFile, uploadableFile,
place, place,
@ -193,7 +193,7 @@ class UploadRepository @Inject constructor(
* @param location Location of the image * @param location Location of the image
* @return Quality of UploadItem * @return Quality of UploadItem
*/ */
fun getImageQuality(uploadItem: UploadItem, location: LatLng?): Single<Int> { fun getImageQuality(uploadItem: UploadItem, location: LatLng?): Single<Int>? {
return uploadModel.getImageQuality(uploadItem, location) return uploadModel.getImageQuality(uploadItem, location)
} }
@ -213,7 +213,7 @@ class UploadRepository @Inject constructor(
* @param uploadItem UploadItem whose caption is to be checked * @param uploadItem UploadItem whose caption is to be checked
* @return Quality of caption of the UploadItem * @return Quality of caption of the UploadItem
*/ */
fun getCaptionQuality(uploadItem: UploadItem): Single<Int> { fun getCaptionQuality(uploadItem: UploadItem): Single<Int>? {
return uploadModel.getCaptionQuality(uploadItem) return uploadModel.getCaptionQuality(uploadItem)
} }

View file

@ -471,18 +471,20 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.apply() editor.apply()
} }
/** companion object {
* Create Locale based on different types of language codes /**
* @param languageCode * Create Locale based on different types of language codes
* @return Locale and throws error for invalid language codes * @param languageCode
*/ * @return Locale and throws error for invalid language codes
fun createLocale(languageCode: String): Locale { */
val parts = languageCode.split("-") fun createLocale(languageCode: String): Locale {
return when (parts.size) { val parts = languageCode.split("-")
1 -> Locale(parts[0]) return when (parts.size) {
2 -> Locale(parts[0], parts[1]) 1 -> Locale(parts[0])
3 -> Locale(parts[0], parts[1], parts[2]) 2 -> Locale(parts[0], parts[1])
else -> throw IllegalArgumentException("Invalid language code: $languageCode") 3 -> Locale(parts[0], parts[1], parts[2])
else -> throw IllegalArgumentException("Invalid language code: $languageCode")
}
} }
} }

View file

@ -16,7 +16,7 @@ class ImageUtilsWrapper @Inject constructor() {
fun checkImageGeolocationIsDifferent( fun checkImageGeolocationIsDifferent(
geolocationOfFileString: String, geolocationOfFileString: String,
latLng: LatLng latLng: LatLng?
): Single<Int> { ): Single<Int> {
return Single.fromCallable { return Single.fromCallable {
ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng) ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng)

View file

@ -1,9 +1,5 @@
package fr.free.nrw.commons.utils package fr.free.nrw.commons.utils
import org.apache.commons.lang3.StringUtils
import java.util.ArrayList
object MediaDataExtractorUtil { object MediaDataExtractorUtil {
/** /**
@ -13,8 +9,8 @@ object MediaDataExtractorUtil {
* @return * @return
*/ */
@JvmStatic @JvmStatic
fun extractCategoriesFromList(source: String): List<String> { fun extractCategoriesFromList(source: String?): List<String> {
if (source.isBlank()) { if (source.isNullOrBlank()) {
return emptyList() return emptyList()
} }
val cats = source.split("|") val cats = source.split("|")

View file

@ -16,6 +16,7 @@ import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.auth.login.LoginResult import fr.free.nrw.commons.auth.login.LoginResult
import fr.free.nrw.commons.createTestClient import fr.free.nrw.commons.createTestClient
import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.kvstore.JsonKvStore
import io.mockk.mockk
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -66,11 +67,11 @@ class LoginActivityUnitTests {
@Mock @Mock
private lateinit var account: Account private lateinit var account: Account
@Mock
private lateinit var loginResult: LoginResult private lateinit var loginResult: LoginResult
@Before @Before
fun setUp() { fun setUp() {
loginResult = mockk()
MockitoAnnotations.openMocks(this) MockitoAnnotations.openMocks(this)
OkHttpConnectionFactory.CLIENT = createTestClient() OkHttpConnectionFactory.CLIENT = createTestClient()
activity = Robolectric.buildActivity(LoginActivity::class.java).create().get() activity = Robolectric.buildActivity(LoginActivity::class.java).create().get()

View file

@ -7,13 +7,14 @@ import androidx.test.core.app.ApplicationProvider
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.auth.login.LoginResult import fr.free.nrw.commons.auth.login.LoginResult
import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.kvstore.JsonKvStore
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.powermock.api.mockito.PowerMockito.`when`
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@ -33,7 +34,6 @@ class SessionManagerUnitTests {
@Mock @Mock
private lateinit var defaultKvStore: JsonKvStore private lateinit var defaultKvStore: JsonKvStore
@Mock
private lateinit var loginResult: LoginResult private lateinit var loginResult: LoginResult
@Mock @Mock
@ -41,6 +41,7 @@ class SessionManagerUnitTests {
@Before @Before
fun setUp() { fun setUp() {
loginResult = mockk()
MockitoAnnotations.openMocks(this) MockitoAnnotations.openMocks(this)
accountManager = AccountManager.get(ApplicationProvider.getApplicationContext()) accountManager = AccountManager.get(ApplicationProvider.getApplicationContext())
shadowOf(accountManager).addAccount(account) shadowOf(accountManager).addAccount(account)
@ -68,8 +69,8 @@ class SessionManagerUnitTests {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testUpdateAccount() { fun testUpdateAccount() {
`when`(loginResult.userName).thenReturn("username") every { loginResult.userName } returns "username"
`when`(loginResult.password).thenReturn("password") every { loginResult.password } returns "password"
val method: Method = val method: Method =
SessionManager::class.java.getDeclaredMethod( SessionManager::class.java.getDeclaredMethod(
"updateAccount", "updateAccount",

View file

@ -85,7 +85,7 @@ class RecentLanguagesDaoUnitTest {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())) whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull()))
.thenReturn(createCursor(14)) .thenReturn(createCursor(14))
val result = testObject.recentLanguages val result = testObject.getRecentLanguages()
Assert.assertEquals(14, (result.size)) Assert.assertEquals(14, (result.size))
} }
@ -95,20 +95,20 @@ class RecentLanguagesDaoUnitTest {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow( whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(
RemoteException(""), RemoteException(""),
) )
testObject.recentLanguages testObject.getRecentLanguages()
} }
@Test @Test
fun getGetRecentLanguagesReturnsEmptyList_emptyCursor() { fun getGetRecentLanguagesReturnsEmptyList_emptyCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())) whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull()))
.thenReturn(createCursor(0)) .thenReturn(createCursor(0))
Assert.assertTrue(testObject.recentLanguages.isEmpty()) Assert.assertTrue(testObject.getRecentLanguages().isEmpty())
} }
@Test @Test
fun getGetRecentLanguagesReturnsEmptyList_nullCursor() { fun getGetRecentLanguagesReturnsEmptyList_nullCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null) whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null)
Assert.assertTrue(testObject.recentLanguages.isEmpty()) Assert.assertTrue(testObject.getRecentLanguages().isEmpty())
} }
@Test @Test
@ -117,7 +117,7 @@ class RecentLanguagesDaoUnitTest {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor) whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false) whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.recentLanguages testObject.getRecentLanguages()
verify(mockCursor).close() verify(mockCursor).close()
} }

View file

@ -19,7 +19,7 @@ import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.recentlanguages.Language import fr.free.nrw.commons.recentlanguages.Language
import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
import fr.free.nrw.commons.settings.SettingsFragment.createLocale import fr.free.nrw.commons.settings.SettingsFragment.Companion.createLocale
import org.junit.Assert import org.junit.Assert
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
@ -156,14 +156,14 @@ class SettingsFragmentUnitTests {
) )
method.isAccessible = true method.isAccessible = true
method.invoke(fragment, "appUiDefaultLanguagePref") method.invoke(fragment, "appUiDefaultLanguagePref")
verify(recentLanguagesDao, times(1)).recentLanguages verify(recentLanguagesDao, times(1)).getRecentLanguages()
} }
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun `Test prepareAppLanguages when recently used languages is not empty`() { fun `Test prepareAppLanguages when recently used languages is not empty`() {
Shadows.shadowOf(Looper.getMainLooper()).idle() Shadows.shadowOf(Looper.getMainLooper()).idle()
whenever(recentLanguagesDao.recentLanguages) whenever(recentLanguagesDao.getRecentLanguages())
.thenReturn( .thenReturn(
mutableListOf( mutableListOf(
Language("English", "en"), Language("English", "en"),
@ -181,7 +181,7 @@ class SettingsFragmentUnitTests {
) )
method.isAccessible = true method.isAccessible = true
method.invoke(fragment, "appUiDefaultLanguagePref") method.invoke(fragment, "appUiDefaultLanguagePref")
verify(recentLanguagesDao, times(2)).recentLanguages verify(recentLanguagesDao, times(2)).getRecentLanguages()
} }
@Test @Test

View file

@ -131,7 +131,7 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun getImageQualityTest() { fun getImageQualityTest() {
whenever(repository.uploads).thenReturn(listOf(uploadItem)) whenever(repository.getUploads()).thenReturn(listOf(uploadItem))
whenever(repository.getImageQuality(uploadItem, location)) whenever(repository.getImageQuality(uploadItem, location))
.thenReturn(testSingleImageResult) .thenReturn(testSingleImageResult)
whenever(uploadItem.imageQuality).thenReturn(0) whenever(uploadItem.imageQuality).thenReturn(0)
@ -149,7 +149,7 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun `get ImageQuality Test while coordinates equals to null`() { fun `get ImageQuality Test while coordinates equals to null`() {
whenever(repository.uploads).thenReturn(listOf(uploadItem)) whenever(repository.getUploads()).thenReturn(listOf(uploadItem))
whenever(repository.getImageQuality(uploadItem, location)) whenever(repository.getImageQuality(uploadItem, location))
.thenReturn(testSingleImageResult) .thenReturn(testSingleImageResult)
whenever(uploadItem.imageQuality).thenReturn(0) whenever(uploadItem.imageQuality).thenReturn(0)
@ -225,7 +225,7 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun fetchImageAndTitleTest() { fun fetchImageAndTitleTest() {
whenever(repository.uploads).thenReturn(listOf(uploadItem)) whenever(repository.getUploads()).thenReturn(listOf(uploadItem))
whenever(repository.getUploadItem(ArgumentMatchers.anyInt())) whenever(repository.getUploadItem(ArgumentMatchers.anyInt()))
.thenReturn(uploadItem) .thenReturn(uploadItem)
whenever(uploadItem.uploadMediaDetails).thenReturn(listOf()) whenever(uploadItem.uploadMediaDetails).thenReturn(listOf())

View file

@ -15,6 +15,7 @@ import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.structure.depictions.DepictModel import fr.free.nrw.commons.upload.structure.depictions.DepictModel
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -196,7 +197,7 @@ class UploadRepositoryUnitTest {
fun testGetCaptionQuality() { fun testGetCaptionQuality() {
assertEquals( assertEquals(
repository.getCaptionQuality(uploadItem), repository.getCaptionQuality(uploadItem),
uploadModel.getCaptionQuality(uploadItem), uploadModel.getCaptionQuality(uploadItem)
) )
} }
@ -386,7 +387,8 @@ class UploadRepositoryUnitTest {
fun testGetCategories() { fun testGetCategories() {
assertEquals( assertEquals(
repository.getCategories(listOf("Test")), repository.getCategories(listOf("Test")),
categoriesModel.getCategoriesByName(mutableListOf("Test")), categoriesModel.getCategoriesByName(mutableListOf("Test"))
?: Observable.empty<List<CategoryItem>>()
) )
} }
} }

View file

@ -1,10 +1,10 @@
package fr.free.nrw.commons.upload.structure.depictions package fr.free.nrw.commons.upload.structure.depictions
import com.nhaarman.mockitokotlin2.mock
import depictedItem import depictedItem
import entity import entity
import entityId import entityId
import fr.free.nrw.commons.wikidata.WikidataProperties import fr.free.nrw.commons.wikidata.WikidataProperties
import io.mockk.mockk
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import place import place
@ -53,7 +53,7 @@ class DepictedItemTest {
entity( entity(
statements = statements =
mapOf( mapOf(
WikidataProperties.IMAGE.propertyName to listOf(statement(snak(dataValue = mock()))), WikidataProperties.IMAGE.propertyName to listOf(statement(snak(dataValue = mockk()))),
), ),
), ),
).imageUrl, ).imageUrl,