diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8b62cd842c..51b9a8defb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,6 +6,8 @@ plugins { id("com.android.application") kotlin("android") kotlin("plugin.serialization") version "1.9.22" + kotlin("plugin.allopen") version "1.9.22" + id("com.google.devtools.ksp") version "1.9.22-1.0.16" } android { @@ -83,6 +85,19 @@ android { namespace = "de.westnordost.streetcomplete" } +val taskIsRunningTest = gradle.startParameter.taskNames + .any { it == "check" || it.startsWith("test") || it.contains("Test") } + +//if (taskIsRunningTest) { + allOpen { + annotation("de.westnordost.streetcomplete.util.Mockable") + } +//} + +// allOpen { +// annotation("kotlin.Metadata") +// } + val keystorePropertiesFile = rootProject.file("keystore.properties") if (keystorePropertiesFile.exists()) { val props = Properties() @@ -116,6 +131,12 @@ dependencies { testImplementation("org.mockito:mockito-core:$mockitoVersion") testImplementation("org.mockito:mockito-inline:$mockitoVersion") testImplementation(kotlin("test")) + testImplementation("io.mockative:mockative:2.2.2") + configurations + .filter { it.name.startsWith("ksp") && it.name.contains("Test") } + .forEach { + add(it.name, "io.mockative:mockative-processor:2.2.2") + } androidTestImplementation("androidx.test:runner:1.5.2") androidTestImplementation("androidx.test:rules:1.5.0") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/AllEditTypes.kt b/app/src/main/java/de/westnordost/streetcomplete/data/AllEditTypes.kt index c6fb89f06a..f80f191e6a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/AllEditTypes.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/AllEditTypes.kt @@ -1,7 +1,9 @@ package de.westnordost.streetcomplete.data import de.westnordost.streetcomplete.data.osm.edits.EditType +import de.westnordost.streetcomplete.util.Mockable +@Mockable class AllEditTypes( registries: List> ) : AbstractCollection() { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesController.kt index 2ebda607f4..2aa0b078c4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesController.kt @@ -2,8 +2,10 @@ package de.westnordost.streetcomplete.data.download.tiles import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds +@Mockable class DownloadedTilesController( private val dao: DownloadedTilesDao ) : DownloadedTilesSource { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt index 63f2dfd071..e4974f48e4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt @@ -5,9 +5,11 @@ import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Co import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.X import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.Y import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.NAME +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds /** Keeps info in which areas things have been downloaded already in a tile grid */ +@Mockable class DownloadedTilesDao(private val db: Database) { /** Persist that the given tile range has been downloaded (now) */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryController.kt index fcc16a71a3..a29cce3d5d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryController.kt @@ -15,9 +15,11 @@ import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestHidden import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestsHiddenController import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestsHiddenSource import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds /** All edits done by the user in one place: Edits made on notes, on map data, hidings of quests */ +@Mockable class EditHistoryController( private val elementEditsController: ElementEditsController, private val noteEditsController: NoteEditsController, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/ElementFilter.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/ElementFilter.kt index e4ab2a6b1b..1cd5561649 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/ElementFilter.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/ElementFilter.kt @@ -5,10 +5,12 @@ import de.westnordost.streetcomplete.data.elementfilter.withOptionalUnitToDouble import de.westnordost.streetcomplete.data.osm.mapdata.Element import de.westnordost.streetcomplete.osm.getLastCheckDateKeys import de.westnordost.streetcomplete.osm.toCheckDate +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.toLocalDate import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate +@Mockable sealed interface ElementFilter : Matcher { abstract override fun toString(): String } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/meta/AndroidAssetAcess.kt b/app/src/main/java/de/westnordost/streetcomplete/data/meta/AndroidAssetAcess.kt new file mode 100644 index 0000000000..4c9a1a71f8 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/meta/AndroidAssetAcess.kt @@ -0,0 +1,13 @@ +package de.westnordost.streetcomplete.data.meta + +import android.content.res.AssetManager + +class AndroidAssetAcess(private val assetManager: AssetManager) : AssetAccess { + override fun list(basepath: String): Array? { + return assetManager.list(basepath) + } + + override fun open(s: String): java.io.InputStream { + return assetManager.open(s) + } +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/meta/AssetAccess.kt b/app/src/main/java/de/westnordost/streetcomplete/data/meta/AssetAccess.kt new file mode 100644 index 0000000000..a205d1c54b --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/meta/AssetAccess.kt @@ -0,0 +1,6 @@ +package de.westnordost.streetcomplete.data.meta + +interface AssetAccess { + abstract fun list(basepath: String): Array? + abstract fun open(s: String): java.io.InputStream +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/meta/CountryInfos.kt b/app/src/main/java/de/westnordost/streetcomplete/data/meta/CountryInfos.kt index ce5bfebc29..83c283ba2d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/meta/CountryInfos.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/meta/CountryInfos.kt @@ -5,10 +5,12 @@ import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration import com.charleskorn.kaml.decodeFromStream import de.westnordost.countryboundaries.CountryBoundaries +import de.westnordost.streetcomplete.util.Mockable import java.io.File import java.io.SequenceInputStream -class CountryInfos(private val assetManager: AssetManager) { +@Mockable +class CountryInfos(private val assetManager: AssetAccess) { private val yaml = Yaml(configuration = YamlConfiguration( strictMode = false, // ignore unknown properties )) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsController.kt index 0471d03ba7..136874a798 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsController.kt @@ -2,7 +2,9 @@ package de.westnordost.streetcomplete.data.osm.created_elements import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import de.westnordost.streetcomplete.util.Mockable +@Mockable class CreatedElementsController( private val db: CreatedElementsDao ) : CreatedElementsSource { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsDao.kt index 9f7e9a9995..1b862b43b1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/created_elements/CreatedElementsDao.kt @@ -7,8 +7,10 @@ import de.westnordost.streetcomplete.data.osm.created_elements.CreatedElementsTa import de.westnordost.streetcomplete.data.osm.created_elements.CreatedElementsTable.NAME import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import de.westnordost.streetcomplete.util.Mockable /** Persists the keys of the elements created by this app already uploaded to the OSM API */ +@Mockable class CreatedElementsDao(private val db: Database) { fun putAll(entries: Collection) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/EditElementsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/EditElementsDao.kt index f832a00235..9b7e4bbace 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/EditElementsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/EditElementsDao.kt @@ -7,7 +7,9 @@ import de.westnordost.streetcomplete.data.osm.edits.EditElementsTable.Columns.EL import de.westnordost.streetcomplete.data.osm.edits.EditElementsTable.NAME import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import de.westnordost.streetcomplete.util.Mockable +@Mockable class EditElementsDao(private val db: Database) { fun put(id: Long, elementKeys: List) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEdit.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEdit.kt index 977389820e..cd297d3d0d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEdit.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEdit.kt @@ -4,6 +4,7 @@ import de.westnordost.streetcomplete.data.edithistory.Edit import de.westnordost.streetcomplete.data.edithistory.ElementEditKey import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.util.Mockable data class ElementEdit( /** (row) id of the edit. 0 if not inserted into DB yet */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsController.kt index 5c23cd3c7a..0a71552661 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsController.kt @@ -5,9 +5,11 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.MapDataUpdates import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.logs.Log +@Mockable class ElementEditsController( private val editsDB: ElementEditsDao, private val editElementsDB: EditElementsDao, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsDao.kt index a6bcdae6dc..dca2046d59 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsDao.kt @@ -24,6 +24,7 @@ import de.westnordost.streetcomplete.data.osm.edits.move.RevertMoveNodeAction import de.westnordost.streetcomplete.data.osm.edits.split_way.SplitWayAction import de.westnordost.streetcomplete.data.osm.edits.update_tags.RevertUpdateElementTagsAction import de.westnordost.streetcomplete.data.osm.edits.update_tags.UpdateElementTagsAction +import de.westnordost.streetcomplete.util.Mockable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -31,6 +32,7 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass +@Mockable class ElementEditsDao( private val db: Database, private val allEditTypes: AllEditTypes, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProvider.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProvider.kt index 441028e8cb..b6f6ba3d01 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProvider.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProvider.kt @@ -2,8 +2,10 @@ package de.westnordost.streetcomplete.data.osm.edits import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import de.westnordost.streetcomplete.util.Mockable /** Provides stable element ids for the creation of new elements */ +@Mockable class ElementIdProvider(elementKeys: Collection) { private val nodeIds: MutableList private val wayIds: MutableList diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProviderDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProviderDao.kt index bf7f6ad2a9..2e294ebe18 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProviderDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/ElementIdProviderDao.kt @@ -9,8 +9,10 @@ import de.westnordost.streetcomplete.data.osm.edits.ElementIdProviderTable.NAME import de.westnordost.streetcomplete.data.osm.mapdata.ElementIdUpdate import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType +import de.westnordost.streetcomplete.util.Mockable /** Assigns new element ids for ElementEditActions that create new elements */ +@Mockable class ElementIdProviderDao(private val db: Database) { fun assign(editId: Long, nodeCount: Int, wayCount: Int, relationCount: Int) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt index 1835afb96b..b501568495 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt @@ -28,6 +28,7 @@ import de.westnordost.streetcomplete.data.osm.mapdata.Way import de.westnordost.streetcomplete.data.osm.mapdata.key import de.westnordost.streetcomplete.data.upload.ConflictException import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.math.contains import de.westnordost.streetcomplete.util.math.intersect @@ -35,6 +36,7 @@ import de.westnordost.streetcomplete.util.math.intersect * * This class is threadsafe. * */ +@Mockable class MapDataWithEditsSource internal constructor( private val mapDataController: MapDataController, private val elementEditsController: ElementEditsController, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapEntryChange.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapEntryChange.kt index e4aef8927d..9ee0a2bee5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapEntryChange.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapEntryChange.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.data.osm.edits.update_tags +import de.westnordost.streetcomplete.util.Mockable import kotlinx.serialization.Serializable @Serializable @@ -15,6 +16,7 @@ sealed class StringMapEntryChange { } @Serializable +@Mockable data class StringMapEntryAdd(override val key: String, val value: String) : StringMapEntryChange() { override fun toString() = "ADD \"$key\"=\"$value\"" @@ -25,6 +27,7 @@ data class StringMapEntryAdd(override val key: String, val value: String) : Stri } @Serializable +@Mockable data class StringMapEntryModify(override val key: String, val valueBefore: String, val value: String) : StringMapEntryChange() { override fun toString() = "MODIFY \"$key\"=\"$valueBefore\" -> \"$key\"=\"$value\"" @@ -35,6 +38,7 @@ data class StringMapEntryModify(override val key: String, val valueBefore: Strin } @Serializable +@Mockable data class StringMapEntryDelete(override val key: String, val valueBefore: String) : StringMapEntryChange() { override fun toString() = "DELETE \"$key\"=\"$valueBefore\"" diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploader.kt index 6235de5793..d131590b63 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploader.kt @@ -9,7 +9,9 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MapDataChanges import de.westnordost.streetcomplete.data.osm.mapdata.MapDataController import de.westnordost.streetcomplete.data.osm.mapdata.MapDataUpdates import de.westnordost.streetcomplete.data.upload.ConflictException +import de.westnordost.streetcomplete.util.Mockable +@Mockable class ElementEditUploader( private val changesetManager: OpenChangesetsManager, private val mapDataApi: MapDataApi, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/LastEditTimeStore.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/LastEditTimeStore.kt index c987f4d042..3a12801eab 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/LastEditTimeStore.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/LastEditTimeStore.kt @@ -2,8 +2,10 @@ package de.westnordost.streetcomplete.data.osm.edits.upload import com.russhwolf.settings.ObservableSettings import de.westnordost.streetcomplete.Prefs +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds +@Mockable class LastEditTimeStore(private val prefs: ObservableSettings) { fun touch() { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/ChangesetAutoCloser.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/ChangesetAutoCloser.kt index 438b82f29f..2c6588e6cc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/ChangesetAutoCloser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/ChangesetAutoCloser.kt @@ -6,8 +6,10 @@ import androidx.work.ExistingWorkPolicy.REPLACE import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager +import de.westnordost.streetcomplete.util.Mockable import java.util.concurrent.TimeUnit +@Mockable class ChangesetAutoCloser(private val context: Context) { fun enqueue(delayInMilliseconds: Long) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsDao.kt index 3bf7ab235b..9fd1eee8a6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsDao.kt @@ -9,8 +9,10 @@ import de.westnordost.streetcomplete.data.osm.edits.upload.changesets.OpenChange import de.westnordost.streetcomplete.data.osm.edits.upload.changesets.OpenChangesetsTable.Columns.SOURCE import de.westnordost.streetcomplete.data.osm.edits.upload.changesets.OpenChangesetsTable.NAME import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.util.Mockable /** Keep track of changesets and the date of the last change that has been made to them */ +@Mockable class OpenChangesetsDao(private val db: Database) { fun getAll(): Collection = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManager.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManager.kt index e882dabd80..1586e4e15a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManager.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManager.kt @@ -7,12 +7,14 @@ import de.westnordost.streetcomplete.data.osm.edits.upload.LastEditTimeStore import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osm.mapdata.MapDataApi import de.westnordost.streetcomplete.data.upload.ConflictException +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.logs.Log import de.westnordost.streetcomplete.util.math.distanceTo import java.util.Locale /** Manages the creation and reusage of changesets */ +@Mockable class OpenChangesetsManager( private val mapDataApi: MapDataApi, private val openChangesetsDB: OpenChangesetsDao, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometry.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometry.kt index 2c7c4fd4e9..a8baa3bf50 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometry.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometry.kt @@ -2,12 +2,14 @@ package de.westnordost.streetcomplete.data.osm.geometry import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.math.enclosingBoundingBox import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** Information on the geometry of a quest */ @Serializable +@Mockable sealed class ElementGeometry { abstract val center: LatLon // the bbox should not be serialized, his is why the bounds cannot be a (computed) property directly diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryCreator.kt index 83935f7179..e459cd78b2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryCreator.kt @@ -7,6 +7,7 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MapData import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.data.osm.mapdata.Relation import de.westnordost.streetcomplete.data.osm.mapdata.Way +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.isArea import de.westnordost.streetcomplete.util.math.centerPointOfPolygon import de.westnordost.streetcomplete.util.math.centerPointOfPolyline @@ -14,6 +15,7 @@ import de.westnordost.streetcomplete.util.math.isRingDefinedClockwise import kotlin.collections.ArrayList /** Creates an ElementGeometry from an element and a collection of positions. */ +@Mockable class ElementGeometryCreator { /** Create an ElementGeometry from any element, using the given MapData to find the positions diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryDao.kt index a6ce694bfc..59b998f895 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/ElementGeometryDao.kt @@ -6,10 +6,12 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.NODE import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.RELATION import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.WAY import de.westnordost.streetcomplete.data.osm.mapdata.NodeDao +import de.westnordost.streetcomplete.util.Mockable /** Stores the geometry of elements. Actually, stores nothing, but delegates the work to * WayGeometryDao and RelationDao. Node geometry is never stored separately, but created * from node position. */ +@Mockable class ElementGeometryDao( private val nodeDao: NodeDao, private val wayGeometryDao: WayGeometryDao, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/PolylinesSerializer.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/PolylinesSerializer.kt index c99e67d121..96c4d8beb2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/PolylinesSerializer.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/PolylinesSerializer.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.data.osm.geometry import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.util.Mockable import kotlinx.io.Buffer import kotlinx.io.readByteArray import kotlinx.io.readDouble @@ -18,6 +19,7 @@ import kotlinx.io.writeDouble * ... for every single coordinate that is part of this particular geometry. That's more than 2 * times the size as when using this method. * */ +@Mockable class PolylinesSerializer { fun serialize(polylines: List>): ByteArray { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/RelationGeometryDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/RelationGeometryDao.kt index 88ba4391af..2f8bdd6a7b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/RelationGeometryDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/RelationGeometryDao.kt @@ -10,8 +10,10 @@ import de.westnordost.streetcomplete.data.osm.geometry.RelationGeometryTable.Col import de.westnordost.streetcomplete.data.osm.geometry.RelationGeometryTable.NAME import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.util.Mockable /** Stores the geometry of relations */ +@Mockable class RelationGeometryDao( private val db: Database, private val polylinesSerializer: PolylinesSerializer, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/WayGeometryDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/WayGeometryDao.kt index ec47d5b458..a20339cf7b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/WayGeometryDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/geometry/WayGeometryDao.kt @@ -10,8 +10,10 @@ import de.westnordost.streetcomplete.data.osm.geometry.WayGeometryTable.Columns. import de.westnordost.streetcomplete.data.osm.geometry.WayGeometryTable.NAME import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.util.Mockable /** Stores the geometry of ways */ +@Mockable class WayGeometryDao( private val db: Database, private val polylinesSerializer: PolylinesSerializer, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/Element.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/Element.kt index 98466d233d..c877e64215 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/Element.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/Element.kt @@ -1,9 +1,11 @@ package de.westnordost.streetcomplete.data.osm.mapdata +import de.westnordost.streetcomplete.util.Mockable import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable +@Mockable sealed class Element { abstract val id: Long abstract val version: Int diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDao.kt index 308998a9c1..5290b07950 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDao.kt @@ -3,9 +3,11 @@ package de.westnordost.streetcomplete.data.osm.mapdata import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.NODE import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.RELATION import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.WAY +import de.westnordost.streetcomplete.util.Mockable /** Stores OSM elements. Actually, stores nothing, but delegates the work to a NodeDao, WayDao and * a RelationDao. :-P */ +@Mockable class ElementDao( private val nodeDao: NodeDao, private val wayDao: WayDao, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt index ff3f25f556..587236a970 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt @@ -6,11 +6,13 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryEntry import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.format import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.logs.Log /** Controller to access element data and its geometry and handle updates to it (from OSM API) */ +@Mockable class MapDataController internal constructor( private val nodeDB: NodeDao, private val wayDB: WayDao, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MutableMapDataWithGeometry.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MutableMapDataWithGeometry.kt index ee1f637cc7..2037aef6fe 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MutableMapDataWithGeometry.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MutableMapDataWithGeometry.kt @@ -3,7 +3,9 @@ package de.westnordost.streetcomplete.data.osm.mapdata import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryEntry import de.westnordost.streetcomplete.data.osm.geometry.ElementPointGeometry +import de.westnordost.streetcomplete.util.Mockable +@Mockable class MutableMapDataWithGeometry() : MapDataWithGeometry { constructor(elements: Iterable, geometryEntries: Iterable) : this() { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/NodeDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/NodeDao.kt index c5c70c532f..6750ffbd05 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/NodeDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/NodeDao.kt @@ -12,12 +12,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.NodeTable.Columns.TAGS import de.westnordost.streetcomplete.data.osm.mapdata.NodeTable.Columns.TIMESTAMP import de.westnordost.streetcomplete.data.osm.mapdata.NodeTable.Columns.VERSION import de.westnordost.streetcomplete.data.osm.mapdata.NodeTable.NAME +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** Stores OSM nodes */ +@Mockable class NodeDao(private val db: Database) { fun put(node: Node) { putAll(listOf(node)) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/RelationDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/RelationDao.kt index ecbe7c7040..8fcedbc78a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/RelationDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/RelationDao.kt @@ -12,11 +12,13 @@ import de.westnordost.streetcomplete.data.osm.mapdata.RelationTables.Columns.TYP import de.westnordost.streetcomplete.data.osm.mapdata.RelationTables.Columns.VERSION import de.westnordost.streetcomplete.data.osm.mapdata.RelationTables.NAME import de.westnordost.streetcomplete.data.osm.mapdata.RelationTables.NAME_MEMBERS +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** Stores OSM relations */ +@Mockable class RelationDao(private val db: Database) { fun put(relation: Relation) { putAll(listOf(relation)) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/WayDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/WayDao.kt index c5740ec4aa..22ec9ac5c3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/WayDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/WayDao.kt @@ -10,12 +10,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.WayTables.Columns.TIMESTAM import de.westnordost.streetcomplete.data.osm.mapdata.WayTables.Columns.VERSION import de.westnordost.streetcomplete.data.osm.mapdata.WayTables.NAME import de.westnordost.streetcomplete.data.osm.mapdata.WayTables.NAME_NODES +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** Stores OSM ways */ +@Mockable class WayDao(private val db: Database) { fun put(way: Way) { putAll(listOf(way)) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestDao.kt index 94dac29c8a..8abd8876b1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestDao.kt @@ -14,8 +14,10 @@ import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestTable.Columns.QU import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestTable.NAME import de.westnordost.streetcomplete.data.queryIn import de.westnordost.streetcomplete.data.quest.OsmQuestKey +import de.westnordost.streetcomplete.util.Mockable /** Persists OsmQuest objects, or more specifically, OsmQuestEntry objects */ +@Mockable class OsmQuestDao(private val db: Database) { fun put(quest: OsmQuestDaoEntry) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestsHiddenDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestsHiddenDao.kt index 676c8a904f..5d56ab81aa 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestsHiddenDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestsHiddenDao.kt @@ -9,9 +9,11 @@ import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestsHiddenTable.Col import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestsHiddenTable.Columns.TIMESTAMP import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestsHiddenTable.NAME import de.westnordost.streetcomplete.data.quest.OsmQuestKey +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds /** Persists which osm quests should be hidden (because the user selected so) */ +@Mockable class OsmQuestsHiddenDao(private val db: Database) { fun add(osmQuestKey: OsmQuestKey) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/Note.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/Note.kt index a74bc4154e..b448cdb6fd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/Note.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/Note.kt @@ -2,9 +2,11 @@ package de.westnordost.streetcomplete.data.osmnotes import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.user.User +import de.westnordost.streetcomplete.util.Mockable import kotlinx.serialization.Serializable @Serializable +@Mockable data class Note( val position: LatLon, val id: Long, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt index 674fcce5e7..da367d2953 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt @@ -3,11 +3,13 @@ package de.westnordost.streetcomplete.data.osmnotes import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.format import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.logs.Log /** Manages access to the notes storage */ +@Mockable class NoteController( private val dao: NoteDao ) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteDao.kt index 6c436ceeb1..55da1a900a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteDao.kt @@ -13,11 +13,13 @@ import de.westnordost.streetcomplete.data.osmnotes.NoteTable.Columns.LATITUDE import de.westnordost.streetcomplete.data.osmnotes.NoteTable.Columns.LONGITUDE import de.westnordost.streetcomplete.data.osmnotes.NoteTable.Columns.STATUS import de.westnordost.streetcomplete.data.osmnotes.NoteTable.NAME +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** Stores OSM notes */ +@Mockable class NoteDao(private val db: Database) { fun put(note: Note) { db.replace(NAME, note.toPairs()) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/StreetCompleteImageUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/StreetCompleteImageUploader.kt index 5aa5ddb184..8e59b0d13d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/StreetCompleteImageUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/StreetCompleteImageUploader.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.data.osmnotes import de.westnordost.streetcomplete.data.download.ConnectionException +import de.westnordost.streetcomplete.util.Mockable import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.header @@ -27,6 +28,7 @@ private data class PhotoUploadResponse( /** Upload and activate a list of image paths to an instance of the * StreetComplete image hosting service */ +@Mockable class StreetCompleteImageUploader( private val httpClient: HttpClient, private val baseUrl: String diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsController.kt index a1511e2754..feb1c3608b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsController.kt @@ -6,8 +6,10 @@ import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osmnotes.Note import de.westnordost.streetcomplete.data.osmtracks.Trackpoint import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds +@Mockable class NoteEditsController( private val editsDB: NoteEditsDao ) : NoteEditsSource { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsDao.kt index 445f53cb52..cc25c1f5aa 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsDao.kt @@ -16,10 +16,12 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns. import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.TRACK import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.TYPE import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.NAME +import de.westnordost.streetcomplete.util.Mockable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +@Mockable class NoteEditsDao(private val db: Database) { fun add(edit: NoteEdit): Boolean = db.transaction { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSource.kt index bd5fdc004b..60408569d7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSource.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSource.kt @@ -10,7 +10,9 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction.CREATE import de.westnordost.streetcomplete.data.user.User import de.westnordost.streetcomplete.data.user.UserDataSource import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable +@Mockable class NotesWithEditsSource( private val noteController: NoteController, private val noteEditsSource: NoteEditsSource, diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/NoteQuestsHiddenDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/NoteQuestsHiddenDao.kt index 88c084bd6c..c63293b23f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/NoteQuestsHiddenDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/NoteQuestsHiddenDao.kt @@ -5,9 +5,11 @@ import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.osmnotes.notequests.NoteQuestsHiddenTable.Columns.NOTE_ID import de.westnordost.streetcomplete.data.osmnotes.notequests.NoteQuestsHiddenTable.Columns.TIMESTAMP import de.westnordost.streetcomplete.data.osmnotes.notequests.NoteQuestsHiddenTable.NAME +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds /** Persists which note ids should be hidden (because the user selected so) in the note quest */ +@Mockable class NoteQuestsHiddenDao(private val db: Database) { fun add(noteId: Long) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestTypeRegistry.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestTypeRegistry.kt index de76675e88..11b8267154 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestTypeRegistry.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestTypeRegistry.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.data.quest import de.westnordost.streetcomplete.data.ObjectTypeRegistry +import de.westnordost.streetcomplete.util.Mockable /** Every osm quest needs to be registered here. * @@ -9,4 +10,5 @@ import de.westnordost.streetcomplete.data.ObjectTypeRegistry * It is also used to define a (display) order of the quest types and to assign an ordinal to each * quest type for serialization. */ +@Mockable class QuestTypeRegistry(ordinalsAndEntries: List>) : ObjectTypeRegistry(ordinalsAndEntries) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsModule.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsModule.kt index 1217a8e81a..8ccfaacf11 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsModule.kt @@ -16,9 +16,11 @@ import de.westnordost.streetcomplete.quests.traffic_signals_vibrate.AddTrafficSi import de.westnordost.streetcomplete.quests.way_lit.AddWayLit import de.westnordost.streetcomplete.quests.wheelchair_access.AddWheelchairAccessPublicTransport import de.westnordost.streetcomplete.quests.wheelchair_access.AddWheelchairAccessToilets +import de.westnordost.streetcomplete.util.Mockable import org.koin.core.qualifier.named import org.koin.dsl.module +@Mockable enum class EditTypeAchievement(val id: String) { RARE("rare"), CAR("car"), diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserAchievementsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserAchievementsDao.kt index 198bd27f86..bb7e90fdac 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserAchievementsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserAchievementsDao.kt @@ -4,8 +4,10 @@ import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.user.achievements.UserAchievementsTable.Columns.ACHIEVEMENT import de.westnordost.streetcomplete.data.user.achievements.UserAchievementsTable.Columns.LEVEL import de.westnordost.streetcomplete.data.user.achievements.UserAchievementsTable.NAME +import de.westnordost.streetcomplete.util.Mockable /** Stores which achievement ids have been unlocked by the user and at which level */ +@Mockable class UserAchievementsDao(private val db: Database) { fun getAll(): Map = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserLinksDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserLinksDao.kt index 2e630da755..9558d732b0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserLinksDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/achievements/UserLinksDao.kt @@ -3,8 +3,10 @@ package de.westnordost.streetcomplete.data.user.achievements import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.user.achievements.UserLinksTable.Columns.LINK import de.westnordost.streetcomplete.data.user.achievements.UserLinksTable.NAME +import de.westnordost.streetcomplete.util.Mockable /** Stores which link ids have been unlocked by the user */ +@Mockable class UserLinksDao(private val db: Database) { fun getAll(): List = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/ActiveDatesDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/ActiveDatesDao.kt index 92c69ea6d1..cf6b58d7ae 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/ActiveDatesDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/ActiveDatesDao.kt @@ -3,12 +3,14 @@ package de.westnordost.streetcomplete.data.user.statistics import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.user.statistics.ActiveDaysTable.Columns.DATE import de.westnordost.streetcomplete.data.user.statistics.ActiveDaysTable.NAME +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.systemTimeNow import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime /** Stores the dates at which the user added edits */ +@Mockable class ActiveDatesDao(private val db: Database) { fun replaceAll(dates: List) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/CountryStatisticsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/CountryStatisticsDao.kt index 6f73a0f936..506a722e0d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/CountryStatisticsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/CountryStatisticsDao.kt @@ -5,8 +5,10 @@ import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.user.statistics.CountryStatisticsTables.Columns.COUNTRY_CODE import de.westnordost.streetcomplete.data.user.statistics.CountryStatisticsTables.Columns.RANK import de.westnordost.streetcomplete.data.user.statistics.CountryStatisticsTables.Columns.SUCCEEDED +import de.westnordost.streetcomplete.util.Mockable /** Stores how many quests the user solved in which country */ +@Mockable class CountryStatisticsDao(private val db: Database, private val name: String) { fun getCountryWithBiggestSolvedCount(): CountryStatistics? = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/EditTypeStatisticsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/EditTypeStatisticsDao.kt index 2ac7f87759..0a0c04ba2b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/EditTypeStatisticsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/EditTypeStatisticsDao.kt @@ -4,8 +4,10 @@ import de.westnordost.streetcomplete.data.CursorPosition import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.user.statistics.EditTypeStatisticsTables.Columns.ELEMENT_EDIT_TYPE import de.westnordost.streetcomplete.data.user.statistics.EditTypeStatisticsTables.Columns.SUCCEEDED +import de.westnordost.streetcomplete.util.Mockable /** Stores how many edits of which element type the user did */ +@Mockable class EditTypeStatisticsDao(private val db: Database, private val name: String) { fun getTotalAmount(): Int = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsParser.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsParser.kt index 1bf5f68758..7402998596 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsParser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsParser.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.data.user.statistics +import de.westnordost.streetcomplete.util.Mockable import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.serialization.Serializable @@ -22,6 +23,7 @@ private data class StatisticsDTO( val isAnalyzing: Boolean, ) +@Mockable class StatisticsParser(private val typeAliases: List>) { fun parse(json: String): Statistics = with(decodeFromString(json)) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetsDao.kt index 1be0686d93..39171d20cd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetsDao.kt @@ -4,8 +4,10 @@ import de.westnordost.streetcomplete.data.Database import de.westnordost.streetcomplete.data.visiblequests.QuestPresetsTable.Columns.QUEST_PRESET_ID import de.westnordost.streetcomplete.data.visiblequests.QuestPresetsTable.Columns.QUEST_PRESET_NAME import de.westnordost.streetcomplete.data.visiblequests.QuestPresetsTable.NAME +import de.westnordost.streetcomplete.util.Mockable /** Stores the ids and names of quest presets */ +@Mockable class QuestPresetsDao(private val db: Database) { fun add(name: String): Long = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderDao.kt index 6f0dd43054..b8f63dd358 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderDao.kt @@ -5,8 +5,10 @@ import de.westnordost.streetcomplete.data.visiblequests.QuestTypeOrderTable.Colu import de.westnordost.streetcomplete.data.visiblequests.QuestTypeOrderTable.Columns.BEFORE import de.westnordost.streetcomplete.data.visiblequests.QuestTypeOrderTable.Columns.QUEST_PRESET_ID import de.westnordost.streetcomplete.data.visiblequests.QuestTypeOrderTable.NAME +import de.westnordost.streetcomplete.util.Mockable /** Stores which quest types have been reordered after other quest types by the user */ +@Mockable class QuestTypeOrderDao(private val db: Database) { fun getAll(presetId: Long): List> = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/SelectedQuestPresetStore.kt b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/SelectedQuestPresetStore.kt index 23b806c346..f1c93a5479 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/SelectedQuestPresetStore.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/SelectedQuestPresetStore.kt @@ -2,7 +2,9 @@ package de.westnordost.streetcomplete.data.visiblequests import com.russhwolf.settings.ObservableSettings import de.westnordost.streetcomplete.Prefs +import de.westnordost.streetcomplete.util.Mockable +@Mockable class SelectedQuestPresetStore(private val prefs: ObservableSettings) { fun get(): Long = prefs.getLong(Prefs.SELECTED_QUESTS_PRESET, 0) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/TeamModeQuestFilter.kt b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/TeamModeQuestFilter.kt index 7955a5af78..d08d28c377 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/TeamModeQuestFilter.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/TeamModeQuestFilter.kt @@ -7,9 +7,11 @@ import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuest import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuest import de.westnordost.streetcomplete.data.quest.Quest import de.westnordost.streetcomplete.util.Listeners +import de.westnordost.streetcomplete.util.Mockable /** Controller for filtering all quests that are hidden because they are shown to other users in * team mode. Takes care of persisting team mode settings and notifying listeners about changes */ +@Mockable class TeamModeQuestFilter internal constructor( private val createdElementsSource: CreatedElementsSource, private val prefs: ObservableSettings diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeDao.kt index 68771f2a4b..afd0231273 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeDao.kt @@ -5,8 +5,10 @@ import de.westnordost.streetcomplete.data.visiblequests.VisibleQuestTypeTable.Co import de.westnordost.streetcomplete.data.visiblequests.VisibleQuestTypeTable.Columns.QUEST_TYPE import de.westnordost.streetcomplete.data.visiblequests.VisibleQuestTypeTable.Columns.VISIBILITY import de.westnordost.streetcomplete.data.visiblequests.VisibleQuestTypeTable.NAME +import de.westnordost.streetcomplete.util.Mockable /** Stores which quest types are visible by user selection and which are not */ +@Mockable class VisibleQuestTypeDao(private val db: Database) { fun put(presetId: Long, questTypeName: String, visible: Boolean) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt index 2902b7f9b7..4763f998c3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt @@ -23,6 +23,7 @@ import de.westnordost.streetcomplete.databinding.FragmentQuestAnswerBinding import de.westnordost.streetcomplete.screens.main.bottom_sheet.AbstractBottomSheetFragment import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapOrientationAware import de.westnordost.streetcomplete.util.FragmentViewBindingPropertyDelegate +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.popIn import de.westnordost.streetcomplete.util.ktx.popOut import de.westnordost.streetcomplete.util.ktx.toast diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/data/WayTrafficFlowDao.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/data/WayTrafficFlowDao.kt index 85a6b95a44..19fd4f1070 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/data/WayTrafficFlowDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/data/WayTrafficFlowDao.kt @@ -5,7 +5,9 @@ import de.westnordost.streetcomplete.data.osm.mapdata.WayTables import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowTable.Columns.IS_FORWARD import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowTable.Columns.WAY_ID import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowTable.NAME +import de.westnordost.streetcomplete.util.Mockable +@Mockable class WayTrafficFlowDao(private val db: Database) { fun put(wayId: Long, isForward: Boolean) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/screens/measure/ArSupportChecker.kt b/app/src/main/java/de/westnordost/streetcomplete/screens/measure/ArSupportChecker.kt index 843f86c88d..6df4a54971 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/screens/measure/ArSupportChecker.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/screens/measure/ArSupportChecker.kt @@ -5,8 +5,10 @@ import android.content.Context import android.os.Build import androidx.core.content.getSystemService import de.westnordost.streetcomplete.ApplicationConstants +import de.westnordost.streetcomplete.util.Mockable import de.westnordost.streetcomplete.util.ktx.isPackageInstalled +@Mockable class ArSupportChecker(private val context: Context) { operator fun invoke(): Boolean = hasArMeasureSupport(context) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/Mockable.kt b/app/src/main/java/de/westnordost/streetcomplete/util/Mockable.kt new file mode 100644 index 0000000000..e3709171d3 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/util/Mockable.kt @@ -0,0 +1,5 @@ +package de.westnordost.streetcomplete.util + +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS) +annotation class Mockable diff --git a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt index 2b9613ca40..708bee68b2 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt @@ -1,13 +1,26 @@ package de.westnordost.streetcomplete import de.westnordost.streetcomplete.data.elementfilter.toOverpassQLString +import de.westnordost.streetcomplete.data.meta.CountryInfo import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType +import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegmentsApi +import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowDao import de.westnordost.streetcomplete.quests.questTypeRegistry -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.screens.measure.ArSupportChecker +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.mock + +@Mock +private val dao: WayTrafficFlowDao = mock(classOf()) +@Mock +private val ar: ArSupportChecker = mock(classOf()) fun main() { - val registry = questTypeRegistry(mock(), mock(), mock(), mock(), mock()) + val registry = questTypeRegistry(TrafficFlowSegmentsApi(HttpClient(MockEngine), ""), dao, ar, { _ -> CountryInfo(emptyList()) }, { _ -> null} ) for (questType in registry) { if (questType is OsmElementQuestType<*>) { diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesControllerTest.kt index 3d48286bc0..e4e2cf1353 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesControllerTest.kt @@ -1,10 +1,13 @@ package de.westnordost.streetcomplete.data.download.tiles -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on -import org.mockito.Mockito.anyLong -import org.mockito.Mockito.verify +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.mock + import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -12,13 +15,16 @@ import kotlin.test.assertTrue class DownloadedTilesControllerTest { - private lateinit var dao: DownloadedTilesDao - private lateinit var listener: DownloadedTilesSource.Listener - private lateinit var ctrl: DownloadedTilesController + // @Mock + // val dao = mock(classOf()) + // @Mock val listener = mock(classOf()) + @Mock private lateinit var dao: DownloadedTilesDao + @Mock private lateinit var listener: DownloadedTilesSource.Listener + @Mock private lateinit var ctrl: DownloadedTilesController @BeforeTest fun setUp() { - dao = mock() - listener = mock() + dao = mock(classOf()) + listener = mock(classOf()) ctrl = DownloadedTilesController(dao) ctrl.addListener(listener) } @@ -26,44 +32,45 @@ class DownloadedTilesControllerTest { @Test fun put() { val r = TilesRect(0, 0, 1, 1) ctrl.put(r) - verify(dao).put(r) - verify(listener).onUpdated() + verifyInvokedExactlyOnce { dao.put(r) } + verifyInvokedExactlyOnce { listener.onUpdated() } } @Test fun clear() { ctrl.clear() - verify(dao).deleteAll() - verify(listener).onUpdated() + verifyInvokedExactlyOnce { dao.deleteAll() } + verifyInvokedExactlyOnce { listener.onUpdated() } } @Test fun invalidate() { val t = TilePos(0, 0) ctrl.invalidate(t) - verify(dao).updateTimeNewerThan(eq(t), anyLong()) // hm, difficult to test the exact time... - verify(listener).onUpdated() + verifyInvokedExactlyOnce { dao.updateTimeNewerThan(eq(t), any()) } // hm, difficult to test the exact time... + verifyInvokedExactlyOnce { listener.onUpdated() } } @Test fun invalidateAll() { ctrl.invalidateAll() - verify(dao).updateAllTimesNewerThan(anyLong()) // hm, difficult to test the exact time... - verify(listener).onUpdated() + verifyInvokedExactlyOnce { dao.updateAllTimesNewerThan(any()) } // hm, difficult to test the exact time... + verifyInvokedExactlyOnce { listener.onUpdated() } } @Test fun deleteOlderThan() { + every { dao.deleteOlderThan(123) }.returns(1) ctrl.deleteOlderThan(123L) - verify(dao).deleteOlderThan(123L) - verify(listener).onUpdated() + verifyInvokedExactlyOnce { dao.deleteOlderThan(123L) } + verifyInvokedExactlyOnce { listener.onUpdated() } } @Test fun getAll() { val r = listOf(TilePos(0, 1)) - on(dao.getAll(123)).thenReturn(r) + every { dao.getAll(123) }.returns(r) assertEquals(r, ctrl.getAll(123)) } @Test fun contains() { val r = TilesRect(0, 0, 1, 1) - on(dao.contains(r, 123L)).thenReturn(true) + every { dao.contains(r, 123L) }.returns(true) assertTrue(ctrl.contains(r, 123L)) } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryControllerTest.kt index 573e03c65b..3b2386281e 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/edithistory/EditHistoryControllerTest.kt @@ -9,63 +9,60 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsController import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsSource import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestsHiddenController import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestsHiddenSource -import de.westnordost.streetcomplete.data.quest.OsmQuestKey import de.westnordost.streetcomplete.data.quest.TestQuestTypeA -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.edit -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.noteEdit import de.westnordost.streetcomplete.testutils.noteQuestHidden -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.questHidden -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.verify +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.mock + import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class EditHistoryControllerTest { - private lateinit var elementEditsController: ElementEditsController - private lateinit var noteEditsController: NoteEditsController - private lateinit var osmQuestsHiddenController: OsmQuestsHiddenController - private lateinit var osmNoteQuestsHiddenController: OsmNoteQuestsHiddenController - private lateinit var listener: EditHistorySource.Listener - private lateinit var ctrl: EditHistoryController + @Mock private lateinit var elementEditsController: ElementEditsController + @Mock private lateinit var noteEditsController: NoteEditsController + @Mock private lateinit var osmQuestsHiddenController: OsmQuestsHiddenController + @Mock private lateinit var osmNoteQuestsHiddenController: OsmNoteQuestsHiddenController + @Mock private lateinit var listener: EditHistorySource.Listener + @Mock private lateinit var ctrl: EditHistoryController - private lateinit var elementEditsListener: ElementEditsSource.Listener - private lateinit var noteEditsListener: NoteEditsSource.Listener - private lateinit var hideNoteQuestsListener: OsmNoteQuestsHiddenSource.Listener - private lateinit var hideQuestsListener: OsmQuestsHiddenSource.Listener + @Mock private lateinit var elementEditsListener: ElementEditsSource.Listener + @Mock private lateinit var noteEditsListener: NoteEditsSource.Listener + @Mock private lateinit var hideNoteQuestsListener: OsmNoteQuestsHiddenSource.Listener + @Mock private lateinit var hideQuestsListener: OsmQuestsHiddenSource.Listener @BeforeTest fun setUp() { - elementEditsController = mock() - noteEditsController = mock() - osmQuestsHiddenController = mock() - osmNoteQuestsHiddenController = mock() - listener = mock() - - elementEditsListener = mock() - noteEditsListener = mock() - hideNoteQuestsListener = mock() - hideQuestsListener = mock() - - on(elementEditsController.addListener(any())).then { invocation -> - elementEditsListener = invocation.getArgument(0) - Unit + elementEditsController = mock(classOf()) + noteEditsController = mock(classOf()) + osmQuestsHiddenController = mock(classOf()) + osmNoteQuestsHiddenController = mock(classOf()) + listener = mock(classOf()) + + elementEditsListener = mock(classOf()) + noteEditsListener = mock(classOf()) + hideNoteQuestsListener = mock(classOf()) + hideQuestsListener = mock(classOf()) + + every { elementEditsController.addListener(any()) }.invokes { args -> + elementEditsListener = args[0] as ElementEditsSource.Listener } - on(noteEditsController.addListener(any())).then { invocation -> - noteEditsListener = invocation.getArgument(0) - Unit + every {noteEditsController.addListener(any()) }.invokes { args -> + noteEditsListener = args[0] as NoteEditsSource.Listener } - on(osmNoteQuestsHiddenController.addListener(any())).then { invocation -> - hideNoteQuestsListener = invocation.getArgument(0) - Unit + every {osmNoteQuestsHiddenController.addListener(any())}.invokes { args -> + hideNoteQuestsListener = args[0] as OsmNoteQuestsHiddenSource.Listener } - on(osmQuestsHiddenController.addListener(any())).then { invocation -> - hideQuestsListener = invocation.getArgument(0) - Unit + every { osmQuestsHiddenController.addListener(any()) }.invokes { args -> + hideQuestsListener = args[0] as OsmQuestsHiddenSource.Listener } ctrl = EditHistoryController(elementEditsController, noteEditsController, osmNoteQuestsHiddenController, osmQuestsHiddenController) @@ -80,10 +77,10 @@ class EditHistoryControllerTest { val edit5 = questHidden(timestamp = 100L) val edit6 = noteQuestHidden(timestamp = 120L) - on(elementEditsController.getAll()).thenReturn(listOf(edit1, edit3)) - on(noteEditsController.getAll()).thenReturn(listOf(edit2, edit4)) - on(osmQuestsHiddenController.getAllHiddenNewerThan(anyLong())).thenReturn(listOf(edit5)) - on(osmNoteQuestsHiddenController.getAllHiddenNewerThan(anyLong())).thenReturn(listOf(edit6)) + every { elementEditsController.getAll() }.returns(listOf(edit1, edit3)) + every {noteEditsController.getAll()}.returns(listOf(edit2, edit4)) + every {osmQuestsHiddenController.getAllHiddenNewerThan(any())}.returns(listOf(edit5)) + every {osmNoteQuestsHiddenController.getAllHiddenNewerThan(any()) }.returns(listOf(edit6)) assertEquals( listOf(edit6, edit5, edit4, edit3, edit2, edit1), @@ -93,99 +90,99 @@ class EditHistoryControllerTest { @Test fun `undo element edit`() { val e = edit() - on(elementEditsController.get(e.id)).thenReturn(e) + every { elementEditsController.get(e.id) }.returns(e) ctrl.undo(e.key) - verify(elementEditsController).undo(e) + verifyInvokedExactlyOnce { elementEditsController.undo(e) } } @Test fun `undo note edit`() { val e = noteEdit() - on(noteEditsController.get(e.id)).thenReturn(e) + every { noteEditsController.get(e.id) }.returns(e) ctrl.undo(e.key) - verify(noteEditsController).undo(e) + verifyInvokedExactlyOnce { noteEditsController.undo(e) } } @Test fun `undo hid quest`() { val e = questHidden(ElementType.NODE, 1L, TestQuestTypeA()) - on(osmQuestsHiddenController.getHidden(e.questKey)).thenReturn(e) + every { osmQuestsHiddenController.getHidden(e.questKey) }.returns(e) ctrl.undo(e.key) - verify(osmQuestsHiddenController).unhide(e.questKey) + verifyInvokedExactlyOnce { osmQuestsHiddenController.unhide(e.questKey) } } @Test fun `undo hid note quest`() { val e = noteQuestHidden() - on(osmNoteQuestsHiddenController.getHidden(e.note.id)).thenReturn(e) + every { osmNoteQuestsHiddenController.getHidden(e.note.id) }.returns(e) ctrl.undo(e.key) - verify(osmNoteQuestsHiddenController).unhide(e.note.id) + verifyInvokedExactlyOnce { osmNoteQuestsHiddenController.unhide(e.note.id) } } @Test fun `relays added element edit`() { val e = edit() elementEditsListener.onAddedEdit(e) - verify(listener).onAdded(e) + verifyInvokedExactlyOnce { listener.onAdded(e) } } @Test fun `relays removed element edit`() { val e = edit() elementEditsListener.onDeletedEdits(listOf(e)) - verify(listener).onDeleted(eq(listOf(e))) + verifyInvokedExactlyOnce { listener.onDeleted(eq(listOf(e))) } } @Test fun `relays synced element edit`() { val e = edit() elementEditsListener.onSyncedEdit(e) - verify(listener).onSynced(e) + verifyInvokedExactlyOnce { listener.onSynced(e) } } @Test fun `relays added note edit`() { val e = noteEdit() noteEditsListener.onAddedEdit(e) - verify(listener).onAdded(e) + verifyInvokedExactlyOnce { listener.onAdded(e) } } @Test fun `relays removed note edit`() { val e = noteEdit() noteEditsListener.onDeletedEdits(listOf(e)) - verify(listener).onDeleted(eq(listOf(e))) + verifyInvokedExactlyOnce { listener.onDeleted(eq(listOf(e))) } } @Test fun `relays synced note edit`() { val e = noteEdit() noteEditsListener.onSyncedEdit(e) - verify(listener).onSynced(e) + verifyInvokedExactlyOnce { listener.onSynced(e) } } @Test fun `relays hid quest`() { val e = questHidden() hideQuestsListener.onHid(e) - verify(listener).onAdded(e) + verifyInvokedExactlyOnce { listener.onAdded(e) } } @Test fun `relays unhid quest`() { val e = questHidden() hideQuestsListener.onUnhid(e) - verify(listener).onDeleted(eq(listOf(e))) + verifyInvokedExactlyOnce { listener.onDeleted(eq(listOf(e))) } } @Test fun `relays unhid all quests`() { hideQuestsListener.onUnhidAll() - verify(listener).onInvalidated() + verifyInvokedExactlyOnce { listener.onInvalidated() } } @Test fun `relays hid note quest`() { val e = noteQuestHidden() hideNoteQuestsListener.onHid(e) - verify(listener).onAdded(e) + verifyInvokedExactlyOnce { listener.onAdded(e) } } @Test fun `relays unhid note quest`() { val e = noteQuestHidden() hideNoteQuestsListener.onUnhid(e) - verify(listener).onDeleted(eq(listOf(e))) + verifyInvokedExactlyOnce { listener.onDeleted(eq(listOf(e))) } } @Test fun `relays unhid all note quests`() { hideNoteQuestsListener.onUnhidAll() - verify(listener).onInvalidated() + verifyInvokedExactlyOnce { listener.onInvalidated() } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/filters/CombineFiltersTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/filters/CombineFiltersTest.kt index 204224f49e..34e68ca70c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/filters/CombineFiltersTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/filters/CombineFiltersTest.kt @@ -1,28 +1,33 @@ package de.westnordost.streetcomplete.data.elementfilter.filters -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue class CombineFiltersTest { + // TODO this is causing a compile problem + // @Mock private lateinit var bla: ElementFilter + @Test fun `does not match if one doesn't match`() { - val f1: ElementFilter = mock() - on(f1.matches(any())).thenReturn(true) - val f2: ElementFilter = mock() - on(f2.matches(any())).thenReturn(false) + val f1: ElementFilter = mock(classOf()) + every { f1.matches(any()) }.returns(true) + val f2: ElementFilter = mock(classOf()) + every { f2.matches(any()) }.returns(false) assertFalse(CombineFilters(f1, f2).matches(node())) } @Test fun `does match if all match`() { - val f1: ElementFilter = mock() - on(f1.matches(any())).thenReturn(true) - val f2: ElementFilter = mock() - on(f2.matches(any())).thenReturn(true) + val f1: ElementFilter = mock(classOf()) + every { f1.matches(any()) }.returns(true) + val f2: ElementFilter = mock(classOf()) + every { f2.matches(any()) }.returns(true) assertTrue(CombineFilters(f1, f2).matches(node())) } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsControllerTest.kt index 722609012e..af2ac095a6 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/ElementEditsControllerTest.kt @@ -10,46 +10,49 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.NODE import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.WAY import de.westnordost.streetcomplete.data.osm.mapdata.MapDataUpdates import de.westnordost.streetcomplete.data.quest.TestQuestTypeA -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.edit -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.pGeom +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test class ElementEditsControllerTest { private lateinit var ctrl: ElementEditsController - private lateinit var db: ElementEditsDao - private lateinit var elementsDb: EditElementsDao - private lateinit var listener: ElementEditsSource.Listener - private lateinit var lastEditTimeStore: LastEditTimeStore - private lateinit var idProvider: ElementIdProviderDao + @Mock private lateinit var db: ElementEditsDao + @Mock private lateinit var elementsDb: EditElementsDao + @Mock private lateinit var listener: ElementEditsSource.Listener + @Mock private lateinit var lastEditTimeStore: LastEditTimeStore + @Mock private lateinit var idProvider: ElementIdProviderDao + + // dummy + @Mock private lateinit var action: ElementEditAction @BeforeTest fun setUp() { - db = mock() - on(db.delete(anyLong())).thenReturn(true) - on(db.markSynced(anyLong())).thenReturn(true) - elementsDb = mock() - idProvider = mock() - lastEditTimeStore = mock() - - listener = mock() + db = mock(classOf()) + every { db.delete(any()) }.returns(true) + every { db.markSynced(any()) }.returns(true) + elementsDb = mock(classOf()) + idProvider = mock(classOf()) + lastEditTimeStore = mock(classOf()) + + listener = mock(classOf()) ctrl = ElementEditsController(db, elementsDb, idProvider, lastEditTimeStore) ctrl.addListener(listener) } @Test fun add() { - val action = mock() + val action = mock(classOf()) val elementKeys = listOf(ElementKey(NODE, 1), ElementKey(WAY, 2)) - on(action.newElementsCount).thenReturn(NewElementsCount(1, 2, 3)) - on(action.elementKeys).thenReturn(elementKeys) + every { action.newElementsCount }.returns(NewElementsCount(1, 2, 3)) + every { action.elementKeys }.returns(elementKeys) ctrl.add(QUEST_TYPE, pGeom(), "test", action, true) @@ -57,9 +60,9 @@ class ElementEditsControllerTest { } @Test fun markSyncFailed() { - val edit = edit(action = mock()) + val edit = edit(action = mock(classOf())) - on(idProvider.get(anyLong())).thenReturn(ElementIdProvider(listOf())) + every { idProvider.get(any()) }.returns(ElementIdProvider(listOf())) ctrl.markSyncFailed(edit) @@ -67,7 +70,7 @@ class ElementEditsControllerTest { } @Test fun markSynced() { - val edit0 = edit(action = mock()) + val edit0 = edit(action = mock(classOf())) // upload shall create two elements: node -1 and node -8... val updates = MapDataUpdates(idUpdates = listOf( @@ -77,39 +80,39 @@ class ElementEditsControllerTest { val updatesMap = updates.idUpdates.associate { ElementKey(it.elementType, it.oldElementId) to it.newElementId } // edit 9 uses node -1 -> it must be updated - val edit1Action = mock() - on(edit1Action.elementKeys).thenReturn(listOf(ElementKey(NODE, -1))) + val edit1Action = mock(classOf()) + every { edit1Action.elementKeys }.returns(listOf(ElementKey(NODE, -1))) - val edit1ActionNew = mock() - on(edit1ActionNew.elementKeys).thenReturn(listOf(ElementKey(NODE, 2))) + val edit1ActionNew = mock(classOf()) + every { edit1ActionNew.elementKeys }.returns(listOf(ElementKey(NODE, 2))) - on(edit1Action.idsUpdatesApplied(updatesMap)).thenReturn(edit1ActionNew) + every { edit1Action.idsUpdatesApplied(updatesMap) }.returns(edit1ActionNew) val edit1 = edit(id = 9, action = edit1Action) - on(db.get(9)).thenReturn(edit1) - on(elementsDb.getAllByElement(NODE, -1)).thenReturn(listOf(edit1.id)) + every { db.get(9) }.returns(edit1) + every { elementsDb.getAllByElement(NODE, -1) }.returns(listOf(edit1.id)) // no edit uses node -8, nothing to do here - on(elementsDb.getAllByElement(NODE, -8)).thenReturn(listOf()) + every { elementsDb.getAllByElement(NODE, -8) }.returns(listOf()) ctrl.markSynced(edit0, updates) val edit0Synced = edit0.copy(isSynced = true) // as explained above, edit 9 is get-put and its element keys updated val edit1New = edit1.copy(action = edit1ActionNew) - verify(db).put(eq(edit1New)) - verify(elementsDb).delete(edit1New.id) - verify(elementsDb).put(edit1New.id, edit1New.action.elementKeys) + verifyInvokedExactlyOnce { db.put(edit1New) } + verifyInvokedExactlyOnce { elementsDb.delete(edit1New.id) } + verifyInvokedExactlyOnce { elementsDb.put(edit1New.id, edit1New.action.elementKeys) } - verify(db).markSynced(edit0Synced.id) - verify(idProvider).updateIds(updates.idUpdates) - verify(listener).onSyncedEdit(edit0Synced) + verifyInvokedExactlyOnce { db.markSynced(edit0Synced.id) } + verifyInvokedExactlyOnce { idProvider.updateIds(updates.idUpdates) } + verifyInvokedExactlyOnce { listener.onSyncedEdit(edit0Synced) } } @Test fun `undo unsynced`() { - val edit = edit(action = mock(), isSynced = false) + val edit = edit(action = mock(classOf()), isSynced = false) - on(idProvider.get(anyLong())).thenReturn(ElementIdProvider(listOf())) + every { idProvider.get(any()) }.returns(ElementIdProvider(listOf())) ctrl.undo(edit) @@ -117,32 +120,32 @@ class ElementEditsControllerTest { } @Test fun `delete edits based on the the one being undone`() { - val edit1 = edit(action = mock(), id = 1L) - val edit2 = edit(action = mock(), id = 2L) - val edit3 = edit(action = mock(), id = 3L) - val edit4 = edit(action = mock(), id = 4L) - val edit5 = edit(action = mock(), id = 5L) + val edit1 = edit(action = mock(classOf()), id = 1L) + val edit2 = edit(action = mock(classOf()), id = 2L) + val edit3 = edit(action = mock(classOf()), id = 3L) + val edit4 = edit(action = mock(classOf()), id = 4L) + val edit5 = edit(action = mock(classOf()), id = 5L) - on(idProvider.get(1L)).thenReturn(ElementIdProvider(listOf( + every { idProvider.get(1L) }.returns(ElementIdProvider(listOf( ElementKey(NODE, -1), ElementKey(NODE, -2), ))) - on(idProvider.get(2L)).thenReturn(ElementIdProvider(listOf( + every { idProvider.get(2L) }.returns(ElementIdProvider(listOf( ElementKey(NODE, -3), ))) - on(idProvider.get(3L)).thenReturn(ElementIdProvider(listOf())) - on(idProvider.get(4L)).thenReturn(ElementIdProvider(listOf())) - on(idProvider.get(5L)).thenReturn(ElementIdProvider(listOf())) + every { idProvider.get(3L) }.returns(ElementIdProvider(listOf())) + every { idProvider.get(4L) }.returns(ElementIdProvider(listOf())) + every { idProvider.get(5L) }.returns(ElementIdProvider(listOf())) - on(elementsDb.getAllByElement(NODE, -1)).thenReturn(listOf(2, 3)) - on(elementsDb.getAllByElement(NODE, -2)).thenReturn(listOf(4)) - on(elementsDb.getAllByElement(NODE, -3)).thenReturn(listOf(5)) + every { elementsDb.getAllByElement(NODE, -1) }.returns(listOf(2, 3)) + every { elementsDb.getAllByElement(NODE, -2) }.returns(listOf(4)) + every { elementsDb.getAllByElement(NODE, -3) }.returns(listOf(5)) - on(db.get(1L)).thenReturn(edit1) - on(db.get(2L)).thenReturn(edit2) - on(db.get(3L)).thenReturn(edit3) - on(db.get(4L)).thenReturn(edit4) - on(db.get(5L)).thenReturn(edit5) + every { db.get(1L) }.returns(edit1) + every { db.get(2L) }.returns(edit2) + every { db.get(3L) }.returns(edit3) + every { db.get(4L) }.returns(edit4) + every { db.get(5L) }.returns(edit5) ctrl.undo(edit1) @@ -156,7 +159,7 @@ class ElementEditsControllerTest { val elementIdProvider = ElementIdProvider(listOf()) val revertedEdit = edit.copy(id = 0, action = action.createReverted(elementIdProvider)) - on(idProvider.get(anyLong())).thenReturn(elementIdProvider) + every { idProvider.get(any()) }.returns(elementIdProvider) ctrl.undo(edit) @@ -165,20 +168,20 @@ class ElementEditsControllerTest { } private fun verifyAdd(edit: ElementEdit) { - verify(db).put(any()) - verify(elementsDb).put(edit.id, edit.action.elementKeys) + verifyInvokedExactlyOnce { db.put(any()) } + verifyInvokedExactlyOnce { elementsDb.put(edit.id, edit.action.elementKeys) } val c = edit.action.newElementsCount - verify(idProvider).assign(edit.id, c.nodes, c.ways, c.relations) - verify(listener).onAddedEdit(any()) - verify(lastEditTimeStore).touch() + verifyInvokedExactlyOnce { idProvider.assign(edit.id, c.nodes, c.ways, c.relations) } + verifyInvokedExactlyOnce { listener.onAddedEdit(any()) } + verifyInvokedExactlyOnce { lastEditTimeStore.touch() } } private fun verifyDelete(vararg edits: ElementEdit) { val editIds = edits.map { it.id } - verify(db).deleteAll(eq(editIds)) - verify(idProvider).deleteAll(eq(editIds)) - verify(elementsDb).deleteAll(eq(editIds)) - verify(listener).onDeletedEdits(edits.toList()) + verifyInvokedExactlyOnce { db.deleteAll(editIds) } + verifyInvokedExactlyOnce { idProvider.deleteAll(editIds) } + verifyInvokedExactlyOnce { elementsDb.deleteAll(editIds) } + verifyInvokedExactlyOnce { listener.onDeletedEdits(edits.toList()) } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt index 265bff4a9b..71e0aa8752 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt @@ -16,25 +16,25 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MutableMapDataWithGeometry import de.westnordost.streetcomplete.data.osm.mapdata.RelationMember import de.westnordost.streetcomplete.data.osm.mapdata.key import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.argThat import de.westnordost.streetcomplete.testutils.bbox import de.westnordost.streetcomplete.testutils.edit -import de.westnordost.streetcomplete.testutils.eq import de.westnordost.streetcomplete.testutils.member -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.testutils.pGeom import de.westnordost.streetcomplete.testutils.rel +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder import de.westnordost.streetcomplete.util.math.intersect -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions -import org.mockito.Mockito.verifyNoMoreInteractions +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.matches +import io.mockative.mock +import io.mockative.verify import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -43,8 +43,8 @@ import kotlin.test.assertTrue class MapDataWithEditsSourceTest { - private lateinit var editsCtrl: ElementEditsController - private lateinit var mapDataCtrl: MapDataController + @Mock private lateinit var editsCtrl: ElementEditsController + @Mock private lateinit var mapDataCtrl: MapDataController private val geometryCreator = ElementGeometryCreator() private lateinit var mapData: MutableMapDataWithGeometry @@ -52,45 +52,47 @@ class MapDataWithEditsSourceTest { private lateinit var editsListener: ElementEditsSource.Listener private lateinit var mapDataListener: MapDataController.Listener + @Mock private var action2 = mock(classOf()) + @BeforeTest fun setUp() { - editsCtrl = mock() - mapDataCtrl = mock() + editsCtrl = mock(classOf()) + mapDataCtrl = mock(classOf()) mapData = MutableMapDataWithGeometry() // a trick to get the edit action to apply to anything mapData.putElement(node(-1, pos = p(60.0, 60.0))) - on(editsCtrl.getIdProvider(anyLong())).thenReturn(ElementIdProvider(listOf())) + every { editsCtrl.getIdProvider(any()) }.returns(ElementIdProvider(listOf())) - on(mapDataCtrl.get(any(), anyLong())).thenAnswer { invocation -> - val elementType = invocation.getArgument(0)!! - val elementId = invocation.getArgument(1) + every { mapDataCtrl.get(any(), any()) }.invokes { arguments -> + val elementType = arguments[0] as ElementType + val elementId = arguments[1] as Long when (elementType) { NODE -> mapData.getNode(elementId) WAY -> mapData.getWay(elementId) RELATION -> mapData.getRelation(elementId) } } - on(mapDataCtrl.getNode(anyLong())).thenAnswer { invocation -> - mapData.getNode(invocation.getArgument(0)) + every { mapDataCtrl.getNode(any()) }.invokes { arguments -> + mapData.getNode(arguments[0] as Long) } - on(mapDataCtrl.getWay(anyLong())).thenAnswer { invocation -> - mapData.getWay(invocation.getArgument(0)) + every { mapDataCtrl.getWay(any()) }.invokes { arguments -> + mapData.getWay(arguments[0] as Long) } - on(mapDataCtrl.getRelation(anyLong())).thenAnswer { invocation -> - mapData.getRelation(invocation.getArgument(0)) + every { mapDataCtrl.getRelation(any()) }.invokes { arguments -> + mapData.getRelation(arguments[0] as Long) } - on(mapDataCtrl.getNodes(any())).thenAnswer { invocation -> - invocation.getArgument>(0).mapNotNull { mapData.getNode(it) } + every { mapDataCtrl.getNodes(any()) }.invokes { arguments -> + (arguments[0] as Collection).mapNotNull { mapData.getNode(it) } } - on(mapDataCtrl.getWays(any())).thenAnswer { invocation -> - invocation.getArgument>(0).mapNotNull { mapData.getWay(it) } + every { mapDataCtrl.getWays(any()) }.invokes { arguments -> + (arguments[0] as Collection).mapNotNull { mapData.getWay(it) } } - on(mapDataCtrl.getRelations(any())).thenAnswer { invocation -> - invocation.getArgument>(0).mapNotNull { mapData.getRelation(it) } + every { mapDataCtrl.getRelations(any()) }.invokes { arguments -> + (arguments[0] as Collection).mapNotNull { mapData.getRelation(it) } } - on(mapDataCtrl.getAll(any())).thenAnswer { invocation -> - invocation.getArgument>(0).mapNotNull { + every { mapDataCtrl.getAll(any()) }.invokes { arguments -> + (arguments[0] as Collection).mapNotNull { when (it.type) { NODE -> mapData.getNode(it.id) WAY -> mapData.getWay(it.id) @@ -98,33 +100,33 @@ class MapDataWithEditsSourceTest { } } } - on(mapDataCtrl.getWaysForNode(anyLong())).thenAnswer { invocation -> - val nodeId = invocation.getArgument(0) + every { mapDataCtrl.getWaysForNode(any()) }.invokes { arguments -> + val nodeId = arguments[0] as Long mapData.ways.filter { it.nodeIds.contains(nodeId) } } - on(mapDataCtrl.getRelationsForNode(anyLong())).thenAnswer { invocation -> - val nodeId = invocation.getArgument(0) + every { mapDataCtrl.getRelationsForNode(any()) }.invokes { arguments -> + val nodeId = arguments[0] as Long mapData.relations.filter { r -> r.members.any { it.ref == nodeId && it.type == NODE } } } - on(mapDataCtrl.getRelationsForWay(anyLong())).thenAnswer { invocation -> - val wayId = invocation.getArgument(0) + every { mapDataCtrl.getRelationsForWay(any()) }.invokes { arguments -> + val wayId = arguments[0] as Long mapData.relations.filter { r -> r.members.any { it.ref == wayId && it.type == WAY } } } - on(mapDataCtrl.getRelationsForRelation(anyLong())).thenAnswer { invocation -> - val relationId = invocation.getArgument(0) + every { mapDataCtrl.getRelationsForRelation(any()) }.invokes { arguments -> + val relationId = arguments[0] as Long mapData.relations.filter { r -> r.members.any { it.ref == relationId && it.type == RELATION } } } - on(mapDataCtrl.getGeometry(any(), anyLong())).thenAnswer { invocation -> - val elementType = invocation.getArgument(0)!! - val elementId = invocation.getArgument(1) + every { mapDataCtrl.getGeometry(any(), any()) }.invokes { arguments -> + val elementType = arguments[0] as ElementType + val elementId = arguments[1] as Long when (elementType) { NODE -> mapData.getNodeGeometry(elementId) WAY -> mapData.getWayGeometry(elementId) RELATION -> mapData.getRelationGeometry(elementId) } } - on(mapDataCtrl.getGeometries(any())).thenAnswer { invocation -> - val keys = invocation.getArgument>(0)!! + every { mapDataCtrl.getGeometries(any()) }.invokes { arguments -> + val keys = arguments[0] as Collection keys.mapNotNull { key -> when (key.type) { NODE -> mapData.getNodeGeometry(key.id) @@ -135,8 +137,8 @@ class MapDataWithEditsSourceTest { } } } - on(mapDataCtrl.getMapDataWithGeometry(any())).thenAnswer { invocation -> - val bbox = invocation.getArgument(0) + every { mapDataCtrl.getMapDataWithGeometry(any()) }.invokes { arguments -> + val bbox = arguments[0] as BoundingBox val result = MutableMapDataWithGeometry() for (element in mapData) { val geometry = when (element.type) { @@ -151,13 +153,13 @@ class MapDataWithEditsSourceTest { result } - on(editsCtrl.addListener(any())).then { invocation -> - editsListener = invocation.getArgument(0) + every { editsCtrl.addListener(any()) }.invokes { arguments -> + editsListener = arguments[0] as ElementEditsSource.Listener Unit } - on(mapDataCtrl.addListener(any())).then { invocation -> - mapDataListener = invocation.getArgument(0) + every { mapDataCtrl.addListener(any()) }.invokes { arguments -> + mapDataListener = arguments[0] as MapDataController.Listener Unit } } @@ -204,11 +206,11 @@ class MapDataWithEditsSourceTest { originalElementsAre(nd) - val action2 = mock() - on(action2.createUpdates(any(), any())).thenReturn(MapDataChanges(modifications = listOf(nd2))) - val action3 = mock() - on(action3.createUpdates(any(), any())).thenReturn(MapDataChanges(modifications = listOf(nd3))) - on(editsCtrl.getAllUnsynced()).thenReturn(listOf( + val action2 = mock(classOf()) + every { action2.createUpdates(any(), any()) }.returns(MapDataChanges(modifications = listOf(nd2))) + val action3 = mock(classOf()) + every { action3.createUpdates(any(), any()) }.returns(MapDataChanges(modifications = listOf(nd3))) + every { editsCtrl.getAllUnsynced() }.returns(listOf( edit(action = action2), edit(action = action3), )) @@ -235,9 +237,9 @@ class MapDataWithEditsSourceTest { originalElementsAre(nd) - val action = mock() - on(action.createUpdates(any(), any())).thenThrow(ConflictException()) - on(editsCtrl.getAllUnsynced()).thenReturn(listOf(edit(action = action))) + val action = mock(classOf()) + every { action.createUpdates(any(), any()) }.throws(ConflictException()) + every { editsCtrl.getAllUnsynced() }.returns(listOf(edit(action = action))) val s = create() assertEquals(nd, s.get(NODE, 1)) @@ -305,11 +307,11 @@ class MapDataWithEditsSourceTest { originalElementsAre(nd) originalGeometriesAre(ElementGeometryEntry(NODE, 1, p)) - val action2 = mock() - on(action2.createUpdates(any(), any())).thenReturn(MapDataChanges(modifications = listOf(nd2))) - val action3 = mock() - on(action3.createUpdates(any(), any())).thenReturn(MapDataChanges(modifications = listOf(nd3))) - on(editsCtrl.getAllUnsynced()).thenReturn(listOf( + val action2 = mock(classOf()) + every { action2.createUpdates(any(), any()) }.returns(MapDataChanges(modifications = listOf(nd2))) + val action3 = mock(classOf()) + every { action3.createUpdates(any(), any()) }.returns(MapDataChanges(modifications = listOf(nd3))) + every { editsCtrl.getAllUnsynced() }.returns(listOf( edit(action = action2), edit(action = action3) )) @@ -849,18 +851,18 @@ class MapDataWithEditsSourceTest { @Test fun `onAddedEdit does not relay if no elements were updated`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) editsControllerNotifiesMapDataChangesAdded() - verifyNoInteractions(listener) + // verifyNoInteractions(listener) } @Test fun `onAddedEdit relays updated elements`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val n = node(1, p(1.0, 10.0)) @@ -872,7 +874,7 @@ class MapDataWithEditsSourceTest { listOf(p) ) - verify(listener).onUpdated(updated = eq(expectedMapData), deleted = eq(listOf())) + verifyInvokedExactlyOnce { listener.onUpdated(updated = eq(expectedMapData), deleted = eq(listOf())) } assertEquals(n, s.get(NODE, 1)) assertEquals(p.geometry, s.getGeometry(NODE, 1)) @@ -881,16 +883,16 @@ class MapDataWithEditsSourceTest { @Test fun `onAddedEdit relays deleted elements`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val n = node(1, p(1.0, 10.0)) editsControllerNotifiesMapDataChangesAdded(deletions = listOf(n)) - verify(listener).onUpdated( + verifyInvokedExactlyOnce { listener.onUpdated( updated = eq(MutableMapDataWithGeometry()), deleted = eq(listOf(ElementKey(NODE, 1))) - ) + )} assertNull(s.get(NODE, 1)) assertNull(s.getGeometry(NODE, 1)) @@ -899,7 +901,7 @@ class MapDataWithEditsSourceTest { @Test fun `onAddedEdit relays elements if only their geometry is updated`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val p = p(1.0, 10.0) @@ -919,8 +921,8 @@ class MapDataWithEditsSourceTest { editsListener.onAddedEdit(edit(action = MoveNodeAction(n, pNew))) - verify(listener).onUpdated( - updated = argThat { + verify { listener.onUpdated( + updated = matches { val wgNew = geometryCreator.create(w, listOf(pNew, p2)) it.nodes.single().copy(timestampEdited = 0) == n.copy(position = pNew, timestampEdited = 0) && it.ways.single() == w @@ -930,7 +932,7 @@ class MapDataWithEditsSourceTest { && it.getRelationGeometry(1) == wgNew }, deleted = eq(listOf()) - ) + )} } //endregion @@ -940,7 +942,7 @@ class MapDataWithEditsSourceTest { @Test fun `onDeletedEdits relays updated element`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val n = node(1, p(1.0, 10.0)) @@ -950,16 +952,16 @@ class MapDataWithEditsSourceTest { editsControllerNotifiesDeletedEdit(listOf(n.key), listOf()) - verify(listener).onUpdated( + verifyInvokedExactlyOnce { listener.onUpdated( updated = eq(MutableMapDataWithGeometry(listOf(n), listOf(p))), deleted = eq(listOf()) - ) + )} } @Test fun `onDeletedEdits relays elements created by edit as deleted elements`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val n = node(1, p(1.0, 10.0)) @@ -974,16 +976,16 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(modifications = listOf(n)) editsControllerNotifiesDeletedEdit(listOf(n.key), delElements) - verify(listener).onUpdated( + verifyInvokedExactlyOnce { listener.onUpdated( updated = eq(MutableMapDataWithGeometry(listOf(n), listOf(p))), deleted = eq(delElements) - ) + )} } @Test fun `onDeletedEdit relays elements if only their geometry is updated`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val p = p(1.0, 10.0) @@ -1003,8 +1005,8 @@ class MapDataWithEditsSourceTest { editsListener.onAddedEdit(edit(action = MoveNodeAction(n, pNew))) - verify(listener).onUpdated( - updated = argThat { + verifyInvokedExactlyOnce { listener.onUpdated( + updated = matches { val wgNew = geometryCreator.create(w, listOf(pNew, p2)) it.nodes.single().copy(timestampEdited = 0) == n.copy(position = pNew, timestampEdited = 0) && it.ways.single() == w @@ -1014,11 +1016,11 @@ class MapDataWithEditsSourceTest { && it.getRelationGeometry(1) == wgNew }, deleted = eq(listOf()) - ) + )} editsListener.onDeletedEdits(listOf(edit(action = MoveNodeAction(n, pNew)))) - verify(listener).onUpdated(updated = eq(MutableMapDataWithGeometry(listOf(n, w, r), listOf(g, l, rg))), deleted = eq(listOf())) + verifyInvokedExactlyOnce { listener.onUpdated(updated = eq(MutableMapDataWithGeometry(listOf(n, w, r), listOf(g, l, rg))), deleted = eq(listOf())) } } //endregion @@ -1031,7 +1033,7 @@ class MapDataWithEditsSourceTest { val pNew = ElementGeometryEntry(NODE, 1, pGeom(0.2, 0.0)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val updatedMapData = MutableMapDataWithGeometry( @@ -1050,7 +1052,7 @@ class MapDataWithEditsSourceTest { val expectedDeletions = listOf( ElementKey(NODE, 2) ) - verify(listener).onUpdated(eq(expectedMapDataWithGeometry), eq(expectedDeletions)) + verifyInvokedExactlyOnce { listener.onUpdated(eq(expectedMapDataWithGeometry), eq(expectedDeletions)) } } @Test @@ -1074,7 +1076,7 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(modifications = listOf(ndModified, ndModified4), deletions = listOf(ndModifiedDeleted2)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val updatedMapData = MutableMapDataWithGeometry( @@ -1095,7 +1097,7 @@ class MapDataWithEditsSourceTest { ElementKey(NODE, 2), ElementKey(NODE, 3) ) - verify(listener).onUpdated(eq(expectedMapDataWithGeometry), eq(expectedDeletions)) + verifyInvokedExactlyOnce { listener.onUpdated(eq(expectedMapDataWithGeometry), eq(expectedDeletions)) } } @Test @@ -1103,7 +1105,7 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(deletions = listOf(node(4))) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val updatedMapData = MutableMapDataWithGeometry() @@ -1111,7 +1113,7 @@ class MapDataWithEditsSourceTest { mapDataListener.onUpdated(updatedMapData, deletions) - verifyNoInteractions(listener) + // // verifyNoInteractions(listener) } @Test @@ -1119,7 +1121,7 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(deletions = listOf(node(4))) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val updatedMapData = MutableMapDataWithGeometry() @@ -1130,7 +1132,7 @@ class MapDataWithEditsSourceTest { ElementKey(NODE, 3), ElementKey(NODE, 4) ) - verify(listener).onUpdated(eq(updatedMapData), eq(expectedDeletions)) + verifyInvokedExactlyOnce { listener.onUpdated(eq(updatedMapData), eq(expectedDeletions)) } } @Test @@ -1144,7 +1146,7 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(modifications = listOf(ndModified, ndModified4)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) // simulating that an edit that modifies node 4 is uploaded: @@ -1158,7 +1160,7 @@ class MapDataWithEditsSourceTest { ) mapDataListener.onUpdated(updatedMapData, emptyList()) - verifyNoInteractions(listener) + // verifyNoInteractions(listener) } @Test @@ -1175,7 +1177,7 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(modifications = listOf(ndModified, ndModified4)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) // simulating that an edit that modifies node 1 and node 4 is uploaded: @@ -1189,7 +1191,7 @@ class MapDataWithEditsSourceTest { ) mapDataListener.onUpdated(updatedMapData, emptyList()) - verifyNoMoreInteractions(listener) + // verifyNoMoreInteractions(listener) } @Test @@ -1205,7 +1207,7 @@ class MapDataWithEditsSourceTest { mapDataChangesAre(modifications = listOf(ndModified, ndModified4)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) // simulating that an edit that modifies node 1 is uploaded: @@ -1219,7 +1221,7 @@ class MapDataWithEditsSourceTest { ) mapDataListener.onUpdated(updatedMapData, emptyList()) - verify(listener).onUpdated(eq(updatedMapData), eq(emptyList())) + verifyInvokedExactlyOnce { listener.onUpdated(eq(updatedMapData), eq(emptyList())) } } //endregion @@ -1232,7 +1234,7 @@ class MapDataWithEditsSourceTest { val pNew = ElementGeometryEntry(NODE, 1, pGeom(0.2, 0.0)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) val bbox = bbox() @@ -1246,7 +1248,7 @@ class MapDataWithEditsSourceTest { elements = listOf(ndNewOriginal), geometryEntries = listOf(pNew), ) - verify(listener).onReplacedForBBox(eq(bbox), eq(expectedMapDataWithGeometry)) + verifyInvokedExactlyOnce { listener.onReplacedForBBox(eq(bbox), eq(expectedMapDataWithGeometry)) } } @Test @@ -1260,7 +1262,7 @@ class MapDataWithEditsSourceTest { val pNew2 = ElementGeometryEntry(NODE, 2, pGeom(0.8, 0.1)) val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) mapDataChangesAre(modifications = listOf(ndModified)) @@ -1276,7 +1278,7 @@ class MapDataWithEditsSourceTest { elements = listOf(ndModified, ndNewOriginal2), geometryEntries = listOf(pModified, pNew2), ) - verify(listener).onReplacedForBBox(eq(bbox), eq(expectedMapDataWithGeometry)) + verifyInvokedExactlyOnce { listener.onReplacedForBBox(eq(bbox), eq(expectedMapDataWithGeometry)) } } // I spare myself more tests for onReplacedForBBox here because it internally does the same as @@ -1289,11 +1291,11 @@ class MapDataWithEditsSourceTest { @Test fun `onCleared is passed on`() { val s = create() - val listener = mock() + val listener = mock(classOf()) s.addListener(listener) mapDataListener.onCleared() - verify(listener).onCleared() + verifyInvokedExactlyOnce { listener.onCleared() } } //endregion @@ -1327,9 +1329,9 @@ class MapDataWithEditsSourceTest { modifications: Collection = emptyList(), deletions: Collection = emptyList() ) { - val action = mock() - on(action.createUpdates(any(), any())).thenReturn(MapDataChanges(creations, modifications, deletions)) - on(editsCtrl.getAllUnsynced()).thenReturn(listOf(edit(action = action))) + val action = mock(classOf()) + every { action.createUpdates(any(), any()) }.returns(MapDataChanges(creations, modifications, deletions)) + every { editsCtrl.getAllUnsynced() }.returns(listOf(edit(action = action))) } private fun thereAreNoMapDataChanges() { @@ -1341,15 +1343,15 @@ class MapDataWithEditsSourceTest { modifications: Collection = emptyList(), deletions: Collection = emptyList() ) { - val action = mock() - on(action.createUpdates(any(), any())).thenReturn(MapDataChanges(creations, modifications, deletions)) + val action = mock(classOf()) + every { action.createUpdates(any(), any()) }.returns(MapDataChanges(creations, modifications, deletions)) editsListener.onAddedEdit(edit(action = action)) } private fun editsControllerNotifiesDeletedEdit(editedElementKeys: List, createdElementKeys: List) { - on(editsCtrl.getIdProvider(anyLong())).thenReturn(ElementIdProvider(createdElementKeys)) - val action = mock() - on(action.elementKeys).thenReturn(editedElementKeys) + every { editsCtrl.getIdProvider(any()) }.returns(ElementIdProvider(createdElementKeys)) + val action = mock(classOf()) + every { action.elementKeys }.returns(editedElementKeys) editsListener.onDeletedEdits(listOf(edit(action = action))) } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeActionTest.kt index c77cc04f6e..d83fbd702b 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeActionTest.kt @@ -9,11 +9,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MutableMapData import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.data.osm.mapdata.Way import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.testutils.way +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -22,19 +25,18 @@ import kotlin.test.assertNotEquals import kotlin.test.assertTrue internal class CreateNodeActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = mock(classOf()) + provider = ElementIdProvider(listOf(ElementKey(ElementType.NODE, -123))) } @Test fun `create node`() { - on(provider.nextNodeId()).thenReturn(-123) - val tags = mapOf("amenity" to "atm") val position = LatLon(12.0, 34.0) val action = CreateNodeAction(position, tags) @@ -60,8 +62,7 @@ internal class CreateNodeActionTest { val node1 = node(id = 1, pos = pos1) val node2 = node(id = 2, pos = pos2) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way, node1, node2))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way, node1, node2))) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 1.5) @@ -85,8 +86,7 @@ internal class CreateNodeActionTest { val pos1 = LatLon(0.0, 1.0) val pos2 = LatLon(0.0, 2.0) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(null) + every { repos.getWayComplete(1) }.returns(null) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 1.5) @@ -107,8 +107,7 @@ internal class CreateNodeActionTest { val node2 = node(id = 2, pos = pos2) val node3 = node(id = 3, pos = pos3) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way, node1, node2, node3))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way, node1, node2, node3))) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 1.5) @@ -129,8 +128,7 @@ internal class CreateNodeActionTest { val node2 = node(id = 2, pos = pos2) val node3 = node(id = 3, pos = pos3) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way, node1, node2, node3))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way, node1, node2, node3))) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 1.5) @@ -149,8 +147,7 @@ internal class CreateNodeActionTest { val node1 = node(id = 1, pos = pos1) val node2 = node(id = 2, pos = pos2) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way, node1, node2))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way, node1, node2))) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 1.5) @@ -171,8 +168,7 @@ internal class CreateNodeActionTest { val node1 = node(id = 1, pos = pos1) val node2 = node(id = 2, pos = pos2) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way, node1, node2))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way, node1, node2))) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 1.5) @@ -197,9 +193,8 @@ internal class CreateNodeActionTest { val node3 = node(id = 3, pos = pos3) val node4 = node(id = 4, pos = pos4) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way1, way2, node1, node2))) - on(repos.getWayComplete(2)).thenReturn(MutableMapData(listOf(way2, node3, node4))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way1, way2, node1, node2))) + every { repos.getWayComplete(2) }.returns(MutableMapData(listOf(way2, node3, node4))) val tags = mapOf("entrance" to "yes") val position = LatLon(0.0, 0.0) @@ -230,8 +225,7 @@ internal class CreateNodeActionTest { val node3 = node(id = 3, pos = pos3) val node4 = node(id = 4, pos = pos4) - on(provider.nextNodeId()).thenReturn(-123) - on(repos.getWayComplete(1)).thenReturn(MutableMapData(listOf(way1, node1, node2, node3, node4))) + every { repos.getWayComplete(1) }.returns(MutableMapData(listOf(way1, node1, node2, node3, node4))) val tags = mapOf("entrance" to "yes") val position = LatLon(-1.0, 0.0) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeFromVertexActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeFromVertexActionTest.kt index fd38ab96a1..88d17d8512 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeFromVertexActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/CreateNodeFromVertexActionTest.kt @@ -9,32 +9,36 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.MapDataChanges import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.util.math.translate +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class CreateNodeFromVertexActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun `conflict when node position changed`() { val n = node() val n2 = n.copy(position = n.position.translate(1.0, 0.0)) // moved by 1 meter - on(repos.getNode(n.id)).thenReturn(n2) - on(repos.getWaysForNode(n.id)).thenReturn(listOf()) + every { repos.getNode(n.id) }.returns(n2) + every { repos.getWaysForNode(n.id) }.returns(listOf()) assertFailsWith { CreateNodeFromVertexAction(n, StringMapChanges(listOf()), listOf()) @@ -45,8 +49,8 @@ internal class CreateNodeFromVertexActionTest { @Test fun `conflict when node is not part of exactly the same ways as before`() { val n = node() - on(repos.getNode(n.id)).thenReturn(n) - on(repos.getWaysForNode(n.id)).thenReturn(listOf(way(1), way(2))) + every { repos.getNode(n.id) }.returns(n) + every { repos.getWaysForNode(n.id) }.returns(listOf(way(1), way(2))) assertFailsWith { CreateNodeFromVertexAction(n, StringMapChanges(listOf()), listOf(1L)) @@ -57,8 +61,8 @@ internal class CreateNodeFromVertexActionTest { @Test fun `create updates`() { val n = node() - on(repos.getNode(n.id)).thenReturn(n) - on(repos.getWaysForNode(n.id)).thenReturn(listOf(way(1), way(2))) + every { repos.getNode(n.id) }.returns(n) + every { repos.getWaysForNode(n.id) }.returns(listOf(way(1), way(2))) val changes = StringMapChanges(listOf(StringMapEntryAdd("a", "b"))) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/RevertCreateNodeActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/RevertCreateNodeActionTest.kt index 88c8bde6cf..59ec895115 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/RevertCreateNodeActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/create/RevertCreateNodeActionTest.kt @@ -8,12 +8,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.data.osm.mapdata.Way import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.rel import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.util.math.translate +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -22,19 +24,22 @@ import kotlin.test.assertNotEquals import kotlin.test.assertTrue class RevertCreateNodeActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = io.mockative.mock(classOf()) + provider = elementIdProvider() } @Test fun `revert add node`() { val node = node(123, LatLon(12.0, 34.0), mapOf("amenity" to "atm"), 1) - on(repos.getNode(node.id)).thenReturn(node) + every { repos.getNode(node.id) }.returns(node) + every { repos.getRelationsForNode(node.id) }.returns(listOf()) + every { repos.getWaysForNode(node.id) }.returns(listOf()) val data = RevertCreateNodeAction(node, listOf()).createUpdates(repos, provider) assertTrue(data.creations.isEmpty()) @@ -46,7 +51,7 @@ class RevertCreateNodeActionTest { @Test fun `conflict when node already deleted`() { - on(repos.getNode(1)).thenReturn(null) + every { repos.getNode(1) }.returns(null) assertFailsWith { RevertCreateNodeAction(node(1), listOf()).createUpdates(repos, provider) @@ -57,9 +62,9 @@ class RevertCreateNodeActionTest { fun `conflict when node is now member of a relation`() { val node = node(1) - on(repos.getNode(node.id)).thenReturn(node) - on(repos.getWaysForNode(1)).thenReturn(emptyList()) - on(repos.getRelationsForNode(1)).thenReturn(listOf(rel())) + every { repos.getNode(node.id) }.returns(node) + every { repos.getWaysForNode(1) }.returns(emptyList()) + every { repos.getRelationsForNode(1) }.returns(listOf(rel())) assertFailsWith { RevertCreateNodeAction(node, listOf()).createUpdates(repos, provider) @@ -70,9 +75,9 @@ class RevertCreateNodeActionTest { fun `conflict when node is part of more ways than initially`() { val node = node(1) - on(repos.getNode(node.id)).thenReturn(node) - on(repos.getWaysForNode(1)).thenReturn(listOf(way(1), way(2), way(3))) - on(repos.getRelationsForNode(1)).thenReturn(emptyList()) + every { repos.getNode(node.id) }.returns(node) + every { repos.getWaysForNode(1) }.returns(listOf(way(1), way(2), way(3))) + every { repos.getRelationsForNode(1) }.returns(emptyList()) assertFailsWith { RevertCreateNodeAction(node, listOf(1, 2)).createUpdates(repos, provider) @@ -84,9 +89,9 @@ class RevertCreateNodeActionTest { val node = node(1) val movedNode = node.copy(position = node.position.translate(10.0, 0.0)) - on(repos.getNode(1)).thenReturn(movedNode) - on(repos.getWaysForNode(1)).thenReturn(emptyList()) - on(repos.getRelationsForNode(1)).thenReturn(emptyList()) + every { repos.getNode(1) }.returns(movedNode) + every { repos.getWaysForNode(1) }.returns(emptyList()) + every { repos.getRelationsForNode(1) }.returns(emptyList()) assertFailsWith { RevertCreateNodeAction(node).createUpdates(repos, provider) @@ -97,9 +102,9 @@ class RevertCreateNodeActionTest { fun `conflict when tags changed on node at all`() { val node = node(1) - on(repos.getNode(1)).thenReturn(node.copy(tags = mapOf("different" to "tags"))) - on(repos.getWaysForNode(1)).thenReturn(emptyList()) - on(repos.getRelationsForNode(1)).thenReturn(emptyList()) + every { repos.getNode(1) }.returns(node.copy(tags = mapOf("different" to "tags"))) + every { repos.getWaysForNode(1) }.returns(emptyList()) + every { repos.getRelationsForNode(1) }.returns(emptyList()) assertFailsWith { RevertCreateNodeAction(node).createUpdates(repos, provider) @@ -110,9 +115,9 @@ class RevertCreateNodeActionTest { fun `no conflict when node is part of less ways than initially`() { val node = node(1) - on(repos.getNode(node.id)).thenReturn(node) - on(repos.getWaysForNode(1)).thenReturn(listOf(way(1))) - on(repos.getRelationsForNode(1)).thenReturn(emptyList()) + every { repos.getNode(node.id) }.returns(node) + every { repos.getWaysForNode(1) }.returns(listOf(way(1))) + every { repos.getRelationsForNode(1) }.returns(emptyList()) RevertCreateNodeAction(node, listOf(1, 2)).createUpdates(repos, provider) } @@ -124,9 +129,9 @@ class RevertCreateNodeActionTest { val way1 = way(1, nodes = listOf(1, 2, 3), timestamp = 0) val way2 = way(2, nodes = listOf(4, 1, 6), timestamp = 0) - on(repos.getNode(node.id)).thenReturn(node) - on(repos.getWaysForNode(1)).thenReturn(listOf(way1, way2)) - on(repos.getRelationsForNode(1)).thenReturn(emptyList()) + every { repos.getNode(node.id) }.returns(node) + every { repos.getWaysForNode(1) }.returns(listOf(way1, way2)) + every { repos.getRelationsForNode(1) }.returns(emptyList()) val data = RevertCreateNodeAction(node, listOf(1, 2, 3)).createUpdates(repos, provider) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/DeletePoiNodeActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/DeletePoiNodeActionTest.kt index cafd46993b..d56ff54a94 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/DeletePoiNodeActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/DeletePoiNodeActionTest.kt @@ -5,10 +5,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p +import de.westnordost.streetcomplete.testutils.way +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -19,18 +23,20 @@ class DeletePoiNodeActionTest { private val e = node(1, tags = mutableMapOf("amenity" to "atm"), version = 2) + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider - @BeforeTest fun setUp() { - repos = mock() - provider = mock() + @BeforeTest + fun setUp() { + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun `delete free-floating node`() { - on(repos.getWaysForNode(1L)).thenReturn(emptyList()) - on(repos.getRelationsForNode(1L)).thenReturn(emptyList()) - on(repos.getNode(e.id)).thenReturn(e) + every { repos.getWaysForNode(1L) }.returns(emptyList()) + every { repos.getRelationsForNode(1L) }.returns(emptyList()) + every { repos.getNode(e.id) }.returns(e) val data = DeletePoiNodeAction(e).createUpdates(repos, provider) assertTrue(data.modifications.isEmpty()) assertTrue(data.creations.isEmpty()) @@ -38,9 +44,9 @@ class DeletePoiNodeActionTest { } @Test fun `'delete' vertex`() { - on(repos.getWaysForNode(1L)).thenReturn(listOf(mock())) - on(repos.getRelationsForNode(1L)).thenReturn(emptyList()) - on(repos.getNode(e.id)).thenReturn(e) + every { repos.getWaysForNode(1L) }.returns(listOf(way())) + every { repos.getRelationsForNode(1L) }.returns(emptyList()) + every { repos.getNode(e.id) }.returns(e) val data = DeletePoiNodeAction(e).createUpdates(repos, provider) assertTrue(data.deletions.isEmpty()) assertTrue(data.creations.isEmpty()) @@ -49,7 +55,7 @@ class DeletePoiNodeActionTest { @Test fun `moved element creates conflict`() { - on(repos.getNode(e.id)).thenReturn(e.copy(position = p(1.0, 1.0))) + every { repos.getNode(e.id) }.returns(e.copy(position = p(1.0, 1.0))) assertFailsWith { DeletePoiNodeAction(e).createUpdates(repos, provider) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/RevertDeletePoiNodeActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/RevertDeletePoiNodeActionTest.kt index 5f2c13d552..d388c3797e 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/RevertDeletePoiNodeActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/delete/RevertDeletePoiNodeActionTest.kt @@ -5,10 +5,13 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.util.ktx.copy +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -18,16 +21,18 @@ class RevertDeletePoiNodeActionTest { private val e = node(1, tags = mutableMapOf("amenity" to "atm"), version = 2) + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun `restore deleted element`() { + every { repos.getNode(1) }.returns(e.copy(version = 3)) assertEquals( e.copy( version = 3, @@ -40,7 +45,7 @@ class RevertDeletePoiNodeActionTest { } @Test fun `restore element with cleared tags`() { - on(repos.getNode(1)).thenReturn(e.copy(version = 3)) + every { repos.getNode(1) }.returns(e.copy(version = 3)) assertEquals( e.copy( version = 3, @@ -54,7 +59,7 @@ class RevertDeletePoiNodeActionTest { @Test fun `conflict if there is already a newer version`() { - on(repos.getNode(1)).thenReturn(e.copy(version = 4)) + every { repos.getNode(1) }.returns(e.copy(version = 4)) assertFailsWith { // version 3 would be the deletion diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/MoveNodeActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/MoveNodeActionTest.kt index f7e94e00a3..0777c558ae 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/MoveNodeActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/MoveNodeActionTest.kt @@ -4,11 +4,14 @@ import de.westnordost.streetcomplete.data.osm.edits.ElementIdProvider import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.util.ktx.copy +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -16,19 +19,20 @@ import kotlin.test.assertTrue class MoveNodeActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun moveIt() { val n = node() val p = p(0.0, 1.0) val movedNode = n.copy(position = p) - on(repos.getNode(n.id)).thenReturn(n) + every { repos.getNode(n.id) }.returns(n) val updates = MoveNodeAction(n, p).createUpdates(repos, provider) assertTrue(updates.creations.isEmpty()) assertTrue(updates.deletions.isEmpty()) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/RevertMoveNodeActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/RevertMoveNodeActionTest.kt index 3dca4384cc..70cb06b99a 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/RevertMoveNodeActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/move/RevertMoveNodeActionTest.kt @@ -4,11 +4,14 @@ import de.westnordost.streetcomplete.data.osm.edits.ElementIdProvider import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.util.ktx.copy +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -16,19 +19,21 @@ import kotlin.test.assertTrue class RevertMoveNodeActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider - @BeforeTest fun setUp() { - repos = mock() - provider = mock() + @BeforeTest + fun setUp() { + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun unmoveIt() { val n = node() val p = p(0.0, 1.0) val movedNode = n.copy(position = p) - on(repos.getNode(n.id)).thenReturn(movedNode) + every { repos.getNode(n.id) }.returns(movedNode) val updates = RevertMoveNodeAction(n).createUpdates(repos, provider) assertTrue(updates.creations.isEmpty()) assertTrue(updates.deletions.isEmpty()) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/split_way/SplitWayActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/split_way/SplitWayActionTest.kt index 600c21c2f3..3976408b25 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/split_way/SplitWayActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/split_way/SplitWayActionTest.kt @@ -11,16 +11,17 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MutableMapData import de.westnordost.streetcomplete.data.osm.mapdata.Way import de.westnordost.streetcomplete.data.upload.ConflictException import de.westnordost.streetcomplete.testutils.member -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.testutils.rel import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.testutils.waysAsMembers import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder import de.westnordost.streetcomplete.util.math.createTranslated -import org.mockito.Mockito.reset +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -29,7 +30,7 @@ import kotlin.test.assertTrue class SplitWayActionTest { - private val repos: MapDataRepository = mock() + @Mock private lateinit var repos: MapDataRepository private val p = arrayOf( p(0.0, 0.0), @@ -58,13 +59,13 @@ class SplitWayActionTest { private fun updateRepos(way: Way) { // let the repos return this way and all its nodes on getWayComplete - on(repos.getWayComplete(0)) - .thenReturn(MutableMapData(way.nodeIds.map { nodeId -> n[nodeId.toInt()] }.shuffled() + way)) - on(repos.getRelationsForWay(0)).thenReturn(listOf()) + every { repos.getWayComplete(0) } + .returns(MutableMapData(way.nodeIds.map { nodeId -> n[nodeId.toInt()] }.shuffled() + way)) + every { repos.getRelationsForWay(0) }.returns(listOf()) } @BeforeTest fun setUp() { - reset(repos) + repos = mock(classOf()) updateRepos(way) } @@ -79,7 +80,7 @@ class SplitWayActionTest { @Test fun `raise conflict if way was deleted`() { - on(repos.getWayComplete(0)).thenReturn(null) + every { repos.getWayComplete(0) }.returns(null) assertFailsWith { doSplit(SplitAtPoint(p[1])) @@ -334,7 +335,7 @@ class SplitWayActionTest { } @Test fun `insert all way chunks into relation the way is a member of`() { - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every { repos.getRelationsForWay(0) }.returns(listOf( rel(0, waysAsMembers(listOf(0))) )) val data = doSplit() @@ -345,7 +346,7 @@ class SplitWayActionTest { } @Test fun `insert all way chunks into multiple relations the way is a member of`() { - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getRelationsForWay(0) }.returns(listOf( rel(0, waysAsMembers(listOf(0))), rel(1, waysAsMembers(listOf(0))) )) @@ -358,9 +359,9 @@ class SplitWayActionTest { } @Test fun `insert all way chunks multiple times into relation the way is a member of multiple times`() { - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(0, 5, 0))) - on(repos.getWay(2)).thenReturn(way(2, mutableListOf(3, 4, 3))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(0, 5, 0))) + every {repos.getWay(2)}.returns(way(2, mutableListOf(3, 4, 3))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(0, 1, 0, 2))) )) val data = doSplit() @@ -378,7 +379,7 @@ class SplitWayActionTest { } @Test fun `all way chunks in updated relations have the same role as the original way`() { - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(0), "cool role")), rel(1, waysAsMembers(listOf(0), "not so cool role")) )) @@ -390,9 +391,9 @@ class SplitWayActionTest { @Test fun `insert way chunks at correct position in the updated relation`() { // 4 5 | 0 1 2 3 | 6 7 => 4 5 | 0 1 -1 | -1 2 3 | 6 7 - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(4, 5))) - on(repos.getWay(2)).thenReturn(way(2, mutableListOf(6, 7))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(4, 5))) + every {repos.getWay(2)}.returns(way(2, mutableListOf(6, 7))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(1, 0, 2))) )) val data = doSplit() @@ -411,9 +412,9 @@ class SplitWayActionTest { /* while determining the orientation of the way in the relation, the neighbouring ways are downloaded and analyzed - if they do not exist anymore, this should not lead to a nullpointer exception */ - on(repos.getWay(1)).thenReturn(null) - on(repos.getWay(2)).thenReturn(null) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(null) + every {repos.getWay(2)}.returns(null) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(1, 0, 2))) )) doSplit() @@ -421,8 +422,8 @@ class SplitWayActionTest { @Test fun `insert way chunks backwards in the updated relation as end of reverse chain`() { // 4 3 | 0 1 2 3 => 4 3 | -1 2 3 | 0 1 -1 - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(4, 3))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(4, 3))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(1, 0))) )) val data = doSplit() @@ -438,8 +439,8 @@ class SplitWayActionTest { @Test fun `ignore non-way relation members when determining way orientation in relation`() { // 4 3 | 0 1 2 3 => 4 3 | -1 2 3 | 0 1 -1 - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(4, 3))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(4, 3))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 1), member(NODE, 0), @@ -462,8 +463,8 @@ class SplitWayActionTest { @Test fun `insert way chunks forwards in the updated relation as end of chain`() { // 4 0 | 0 1 2 3 => 4 0 | 0 1 -1 | -1 2 3 - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(4, 0))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(4, 0))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(1, 0))) )) val data = doSplit() @@ -479,8 +480,8 @@ class SplitWayActionTest { @Test fun `insert way chunks backwards in the updated relation as start of reverse chain`() { // 0 1 2 3 | 4 0 => -1 2 3 | 0 1 -1 | 4 0 - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(4, 0))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(4, 0))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(0, 1))) )) val data = doSplit() @@ -496,8 +497,8 @@ class SplitWayActionTest { @Test fun `insert way chunks forwards in the updated relation as start of chain`() { // 0 1 2 3 | 4 3 => 0 1 -1 | -1 2 3 | 4 3 - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(4, 3))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(4, 3))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, waysAsMembers(listOf(0, 1))) )) val data = doSplit() @@ -525,8 +526,8 @@ class SplitWayActionTest { role: String ) { val otherRole = if (role == "from") "to" else "from" - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(3, 4))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(3, 4))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 0, role), member(WAY, 1, otherRole), @@ -563,9 +564,9 @@ class SplitWayActionTest { role: String ) { val otherRole = if (role == "from") "to" else "from" - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(5, 7))) - on(repos.getWay(2)).thenReturn(way(2, mutableListOf(5, 4, 3))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(1)}.returns(way(1, mutableListOf(5, 7))) + every {repos.getWay(2)}.returns(way(2, mutableListOf(5, 4, 3))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 0, role), member(WAY, 1, otherRole), @@ -585,11 +586,11 @@ class SplitWayActionTest { } @Test fun `update a restriction-like relation with split-way and multiple via ways`() { - on(repos.getWay(0)).thenReturn(way(0, mutableListOf(0, 1, 2, 3))) - on(repos.getWay(1)).thenReturn(way(1, mutableListOf(6, 7))) - on(repos.getWay(2)).thenReturn(way(2, mutableListOf(4, 5, 6))) - on(repos.getWay(3)).thenReturn(way(3, mutableListOf(3, 4))) - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getWay(0)}.returns(way(0, mutableListOf(0, 1, 2, 3))) + every {repos.getWay(1)}.returns(way(1, mutableListOf(6, 7))) + every {repos.getWay(2)}.returns(way(2, mutableListOf(4, 5, 6))) + every {repos.getWay(3)}.returns(way(3, mutableListOf(3, 4))) + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 0, "from"), member(WAY, 1, "to"), @@ -610,7 +611,7 @@ class SplitWayActionTest { } @Test fun `no special treatment of restriction relation if the way has another role`() { - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 0, "another role"), member(WAY, 1, "from"), @@ -618,6 +619,8 @@ class SplitWayActionTest { member(WAY, 3, "to") ), mapOf("type" to "restriction")) )) + every {repos.getWay(1)}.returns(way(1, mutableListOf(6, 7))) + every {repos.getWay(3)}.returns(way(3, mutableListOf(3, 4))) val data = doSplit() val relation = data.relations.single() @@ -625,12 +628,13 @@ class SplitWayActionTest { } @Test fun `no special treatment of restriction relation if there is no via`() { - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 0, "from"), member(WAY, 1, "to") ), mapOf("type" to "restriction")) )) + every {repos.getWay(1)}.returns(way(1, mutableListOf(6, 7))) val data = doSplit() val relation = data.relations.single() @@ -638,13 +642,15 @@ class SplitWayActionTest { } @Test fun `no special treatment of restriction relation if from-way does not touch via`() { - on(repos.getRelationsForWay(0)).thenReturn(listOf( + every {repos.getRelationsForWay(0)}.returns(listOf( rel(0, listOf( member(WAY, 0, "from"), member(NODE, 4, "via"), member(WAY, 3, "to") ), mapOf("type" to "restriction")) )) + every {repos.getWay(3)}.returns(way(3, mutableListOf(7, 8))) + every {repos.getWay(4)}.returns(way(4, mutableListOf(6, 7))) val data = doSplit() val relation = data.relations.single() @@ -696,7 +702,7 @@ class SplitWayActionTest { for (i in 1L..counts.ways) { elementKeys.add(ElementKey(WAY, -i)) } for (i in 1L..counts.relations) { elementKeys.add(ElementKey(RELATION, -i)) } val provider = ElementIdProvider(elementKeys) - on(repos.getWay(way.id)).thenReturn(way) + every { repos.getWay(way.id) }.returns(way) val data = action.createUpdates(repos, provider) return MutableMapData(data.creations + data.modifications) } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/RevertUpdateElementTagsActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/RevertUpdateElementTagsActionTest.kt index 5bd4124ec6..3b6fcd9595 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/RevertUpdateElementTagsActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/RevertUpdateElementTagsActionTest.kt @@ -6,11 +6,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementType import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository import de.westnordost.streetcomplete.data.osm.mapdata.Way import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.testutils.way +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.* import kotlin.test.BeforeTest import kotlin.test.Test @@ -18,18 +21,19 @@ import kotlin.test.assertFailsWith class RevertUpdateElementTagsActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun `conflict if node moved too much`() { - on(repos.get(ElementType.NODE, 1)).thenReturn(node(1, p(1.0, 0.0))) + every { repos.get(ElementType.NODE, 1) }.returns(node(1, p(1.0, 0.0))) assertFailsWith { RevertUpdateElementTagsAction( @@ -42,7 +46,7 @@ class RevertUpdateElementTagsActionTest { @Test fun `conflict if changes are not applicable`() { val w = way(1, listOf(1, 2, 3), mutableMapOf("highway" to "residential")) - on(repos.get(ElementType.WAY, 1)).thenReturn(w) + every { repos.get(ElementType.WAY, 1) }.returns(w) assertFailsWith { RevertUpdateElementTagsAction( @@ -54,7 +58,7 @@ class RevertUpdateElementTagsActionTest { @Test fun `apply changes`() { val w = way(1, listOf(1, 2, 3)) - on(repos.get(ElementType.WAY, 1)).thenReturn(w) + every { repos.get(ElementType.WAY, 1) }.returns(w) val data = RevertUpdateElementTagsAction( w, StringMapChanges(listOf(StringMapEntryAdd("highway", "living_street"))) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapChangesTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapChangesTest.kt index e31c52036a..268d647868 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapChangesTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/StringMapChangesTest.kt @@ -1,9 +1,12 @@ package de.westnordost.streetcomplete.data.osm.edits.update_tags -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on -import org.mockito.Mockito.atLeastOnce -import org.mockito.Mockito.verify +import de.westnordost.streetcomplete.testutils.verifyInvokedExactly +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -11,6 +14,7 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class StringMapChangesTest { + @Mock private lateinit var change: StringMapEntryAdd @Test fun empty() { val changes = StringMapChanges(emptyList()) @@ -25,8 +29,9 @@ class StringMapChangesTest { } @Test fun one() { - val change: StringMapEntryChange = mock() - on(change.toString()).thenReturn("x") + change = mock(classOf()) + every { change.toString() }.returns("x") + every { change.conflictsWith(any()) }.returns(false) val changes = StringMapChanges(listOf(change)) val someMap = mutableMapOf("a" to "b") @@ -34,17 +39,23 @@ class StringMapChangesTest { assertEquals("x", changes.toString()) changes.applyTo(someMap) - verify(change).applyTo(someMap) + verifyInvokedExactlyOnce { change.applyTo(someMap) } changes.hasConflictsTo(someMap) - verify(change, atLeastOnce()).conflictsWith(someMap) + verifyInvokedExactly(2) { change.conflictsWith(someMap) } } @Test fun two() { - val change1: StringMapEntryChange = mock() - on(change1.toString()).thenReturn("a") - val change2: StringMapEntryChange = mock() - on(change2.toString()).thenReturn("b") + val change1: StringMapEntryAdd = mock(classOf()) + every {change1.toString()}.returns("a") + every {change1.key}.returns("a") + every {change1.value}.returns("a") + every { change1.conflictsWith(any()) }.returns(false) + val change2: StringMapEntryAdd = mock(classOf()) + every {change2.toString()}.returns("b") + every {change2.key}.returns("b") + every {change2.value}.returns("b") + every { change2.conflictsWith(any()) }.returns(false) val changes = StringMapChanges(listOf(change1, change2)) val someMap = mutableMapOf("a" to "b") @@ -52,20 +63,20 @@ class StringMapChangesTest { assertEquals("a, b", changes.toString()) changes.applyTo(someMap) - verify(change1).applyTo(someMap) - verify(change2).applyTo(someMap) + verifyInvokedExactlyOnce { change1.applyTo(someMap) } + verifyInvokedExactlyOnce { change2.applyTo(someMap) } changes.hasConflictsTo(someMap) - verify(change1, atLeastOnce()).conflictsWith(someMap) - verify(change2, atLeastOnce()).conflictsWith(someMap) + verifyInvokedExactly(2) { change1.conflictsWith(someMap) } + verifyInvokedExactly(2) { change2.conflictsWith(someMap) } } @Test fun `applying with conflict fails`() { val someMap = mutableMapOf() - val conflict: StringMapEntryChange = mock() - on(conflict.conflictsWith(someMap)).thenReturn(true) + val conflict: StringMapEntryAdd = mock(classOf()) + every { conflict.conflictsWith(someMap) }.returns(true) val changes = StringMapChanges(listOf(conflict)) @@ -77,13 +88,32 @@ class StringMapChangesTest { @Test fun getConflicts() { val someMap = emptyMap() - val conflict: StringMapEntryChange = mock() - on(conflict.conflictsWith(someMap)).thenReturn(true) + val conflict: StringMapEntryAdd = mock(classOf()) + every { conflict.conflictsWith(someMap) }.returns(true) + every {conflict.key}.returns("a") + every {conflict.value}.returns("a") - val conflict2: StringMapEntryChange = mock() - on(conflict2.conflictsWith(someMap)).thenReturn(true) + val conflict2: StringMapEntryAdd = mock(classOf()) + every { conflict2.conflictsWith(someMap) }.returns(true) + every {conflict2.key}.returns("b") + every {conflict2.value}.returns("b") - val changes = StringMapChanges(listOf(mock(), mock(), conflict, mock(), conflict2)) + val noConflict1: StringMapEntryAdd = mock(classOf()) + every { noConflict1.conflictsWith(someMap) }.returns(false) + every {noConflict1.key}.returns("c") + every {noConflict1.value}.returns("c") + + val noConflict2: StringMapEntryAdd = mock(classOf()) + every { noConflict2.conflictsWith(someMap) }.returns(false) + every {noConflict2.key}.returns("d") + every {noConflict2.value}.returns("d") + + val noConflict3: StringMapEntryAdd = mock(classOf()) + every { noConflict3.conflictsWith(someMap) }.returns(false) + every {noConflict3.key}.returns("e") + every {noConflict3.value}.returns("e") + + val changes = StringMapChanges(listOf(noConflict1, noConflict2, conflict, noConflict3, conflict2)) changes.getConflictsTo(someMap) @@ -93,8 +123,12 @@ class StringMapChangesTest { } @Test fun equals() { - val a: StringMapEntryChange = mock() - val b: StringMapEntryChange = mock() + val a: StringMapEntryAdd = mock(classOf()) + every {a.key}.returns("a") + every {a.value}.returns("a") + val b: StringMapEntryAdd = mock(classOf()) + every {b.key}.returns("b") + every {b.value}.returns("b") val one = StringMapChanges(listOf(a, b)) val anotherOne = StringMapChanges(listOf(a, b)) val two = StringMapChanges(listOf(b, a)) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/UpdateElementTagsActionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/UpdateElementTagsActionTest.kt index 8fc6eadc73..9b69b3d23f 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/UpdateElementTagsActionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/update_tags/UpdateElementTagsActionTest.kt @@ -6,11 +6,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.* import de.westnordost.streetcomplete.data.osm.mapdata.MapDataRepository import de.westnordost.streetcomplete.data.osm.mapdata.Way import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.elementIdProvider import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.testutils.way +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -18,17 +21,18 @@ import kotlin.test.assertFailsWith class UpdateElementTagsActionTest { + @Mock private lateinit var repos: MapDataRepository private lateinit var provider: ElementIdProvider @BeforeTest fun setUp() { - repos = mock() - provider = mock() + repos = mock(classOf()) + provider = elementIdProvider() } @Test fun `conflict if node moved too much`() { - on(repos.get(NODE, 1)).thenReturn(node(1, p(1.0, 0.0))) + every { repos.get(NODE, 1) }.returns(node(1, p(1.0, 0.0))) assertFailsWith { UpdateElementTagsAction( @@ -41,7 +45,7 @@ class UpdateElementTagsActionTest { @Test fun `conflict if changes are not applicable`() { val w = way(1, listOf(1, 2, 3), mutableMapOf("highway" to "residential")) - on(repos.get(WAY, 1)).thenReturn(w) + every { repos.get(WAY, 1) }.returns(w) assertFailsWith { UpdateElementTagsAction( @@ -53,7 +57,7 @@ class UpdateElementTagsActionTest { @Test fun `apply changes`() { val w = way(1, listOf(1, 2, 3)) - on(repos.get(WAY, 1)).thenReturn(w) + every { repos.get(WAY, 1) }.returns(w) val data = UpdateElementTagsAction( w, StringMapChanges(listOf(StringMapEntryAdd("highway", "living_street"))) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploaderTest.kt index 32c5632376..d0af9884a1 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditUploaderTest.kt @@ -2,78 +2,83 @@ package de.westnordost.streetcomplete.data.osm.edits.upload import de.westnordost.streetcomplete.data.osm.edits.ElementEdit import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction +import de.westnordost.streetcomplete.data.osm.edits.ElementIdProvider import de.westnordost.streetcomplete.data.osm.edits.upload.changesets.OpenChangesetsManager import de.westnordost.streetcomplete.data.osm.mapdata.MapDataApi import de.westnordost.streetcomplete.data.osm.mapdata.MapDataChanges import de.westnordost.streetcomplete.data.osm.mapdata.MapDataController import de.westnordost.streetcomplete.data.osm.mapdata.MapDataUpdates import de.westnordost.streetcomplete.data.upload.ConflictException -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.doThrow +import de.westnordost.streetcomplete.testutils.edit +import de.westnordost.streetcomplete.testutils.elementIdProvider +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertFailsWith class ElementEditUploaderTest { - private lateinit var changesetManager: OpenChangesetsManager - private lateinit var mapDataApi: MapDataApi - private lateinit var mapDataController: MapDataController + @Mock private lateinit var changesetManager: OpenChangesetsManager + @Mock private lateinit var mapDataApi: MapDataApi + @Mock private lateinit var mapDataController: MapDataController private lateinit var uploader: ElementEditUploader + // dummy + @Mock val action: ElementEditAction = mock(classOf()) + @BeforeTest fun setUp() { - changesetManager = mock() - mapDataApi = mock() - mapDataController = mock() + changesetManager = mock(classOf()) + mapDataApi = mock(classOf()) + mapDataController = mock(classOf()) uploader = ElementEditUploader(changesetManager, mapDataApi, mapDataController) } @Test fun `passes on conflict exception`() { - val edit: ElementEdit = mock() - val action: ElementEditAction = mock() - on(edit.action).thenReturn(action) - on(action.createUpdates(any(), any())).thenReturn(MapDataChanges()) - on(mapDataApi.uploadChanges(anyLong(), any(), any())).thenThrow(ConflictException()) + val action: ElementEditAction = mock(classOf()) + val edit: ElementEdit = edit(action = action) + every { changesetManager.getOrCreateChangeset(any(), any(), any(), any()) }.returns(1) + every { changesetManager.createChangeset(any(), any(), any()) }.returns(1) + every { action.createUpdates(any(), any()) }.returns(MapDataChanges()) + every { mapDataApi.uploadChanges(any(), any(), any()) }.throws(ConflictException()) assertFailsWith { - uploader.upload(edit, { mock() }) + uploader.upload(edit, { elementIdProvider() }) } } @Test fun `passes on element conflict exception`() { - val edit: ElementEdit = mock() - val action: ElementEditAction = mock() - on(edit.action).thenReturn(action) - on(action.createUpdates(any(), any())).thenReturn(MapDataChanges()) + val action: ElementEditAction = mock(classOf()) + val edit: ElementEdit = edit(action = action) + every { action.createUpdates(any(), any()) }.returns(MapDataChanges()) - on(changesetManager.getOrCreateChangeset(any(), any(), any(), anyBoolean())).thenReturn(1) - on(changesetManager.createChangeset(any(), any(), any())).thenReturn(1) - on(mapDataApi.uploadChanges(anyLong(), any(), any())) - .thenThrow(ConflictException()) + every { changesetManager.getOrCreateChangeset(any(), any(), any(), any()) }.returns(1) + every { changesetManager.createChangeset(any(), any(), any()) }.returns(1) + every { mapDataApi.uploadChanges(any(), any(), any()) } + .throws(ConflictException()) assertFailsWith { - uploader.upload(edit, { mock() }) + uploader.upload(edit, { elementIdProvider() }) } } @Test fun `handles changeset conflict exception`() { - val edit: ElementEdit = mock() - val action: ElementEditAction = mock() - on(edit.action).thenReturn(action) - on(action.createUpdates(any(), any())).thenReturn(MapDataChanges()) - on(changesetManager.getOrCreateChangeset(any(), any(), any(), anyBoolean())).thenReturn(1) - on(changesetManager.createChangeset(any(), any(), any())).thenReturn(1) - doThrow(ConflictException()).doAnswer { MapDataUpdates() } - .on(mapDataApi).uploadChanges(anyLong(), any(), any()) + val action: ElementEditAction = mock(classOf()) + val edit: ElementEdit = edit(action = action) + every { action.createUpdates(any(), any()) }.returns(MapDataChanges()) + + every { changesetManager.getOrCreateChangeset(any(), any(), any(), any()) }.returns(1) + every { changesetManager.createChangeset(any(), any(), any()) }.returns(1) + every { mapDataApi.uploadChanges(any(), any(), any()) }.throwsMany(ConflictException(), ConflictException()) + every { mapDataApi.uploadChanges(any(), any(), any()) }.returns(MapDataUpdates()) - uploader.upload(edit, { mock() }) + uploader.upload(edit, { elementIdProvider() }) } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditsUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditsUploaderTest.kt index dc59e269e6..2c0d0af121 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditsUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/ElementEditsUploaderTest.kt @@ -11,42 +11,47 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsController import de.westnordost.streetcomplete.data.upload.ConflictException import de.westnordost.streetcomplete.data.upload.OnUploadedChangeListener import de.westnordost.streetcomplete.data.user.statistics.StatisticsController -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.edit -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.mock import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions import kotlin.test.BeforeTest import kotlin.test.Test class ElementEditsUploaderTest { + @Mock private lateinit var elementEditsController: ElementEditsController - private lateinit var mapDataController: MapDataController - private lateinit var noteEditsController: NoteEditsController - private lateinit var singleUploader: ElementEditUploader - private lateinit var mapDataApi: MapDataApi + @Mock private lateinit var mapDataController: MapDataController + @Mock private lateinit var noteEditsController: NoteEditsController + @Mock private lateinit var singleUploader: ElementEditUploader + @Mock private lateinit var mapDataApi: MapDataApi private lateinit var statisticsController: StatisticsController + // dummy required for mockative to generate mock class + @Mock private lateinit var action: ElementEditAction + private lateinit var uploader: ElementEditsUploader private lateinit var listener: OnUploadedChangeListener @BeforeTest fun setUp() { - elementEditsController = mock() - mapDataController = mock() - noteEditsController = mock() + elementEditsController = mock(classOf()) + mapDataController = mock(classOf()) + noteEditsController = mock(classOf()) - singleUploader = mock() - mapDataApi = mock() - statisticsController = mock() + singleUploader = mock(classOf()) + mapDataApi = mock(classOf()) + statisticsController = mock(classOf()) - listener = mock() + listener = mock(classOf()) uploader = ElementEditsUploader(elementEditsController, noteEditsController, mapDataController, singleUploader, mapDataApi, statisticsController) uploader.uploadedChangeListener = listener @@ -55,56 +60,56 @@ class ElementEditsUploaderTest { @Test fun `cancel upload works`() = runBlocking { val job = launch { uploader.upload() } job.cancelAndJoin() - verifyNoInteractions(elementEditsController, mapDataController, singleUploader, statisticsController) + // verifyNoInteractions(elementEditsController, mapDataController, singleUploader, statisticsController) } @Test fun `upload works`() = runBlocking { val edit = edit() - val updates = mock() + val updates = MapDataUpdates() - on(elementEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(singleUploader.upload(any(), any())).thenReturn(updates) + every { elementEditsController.getOldestUnsynced() }.returnsMany(edit) + every { singleUploader.upload(any(), any()) }.returns(updates) uploader.upload() - verify(singleUploader).upload(eq(edit), any()) - verify(listener).onUploaded(any(), any()) - verify(elementEditsController).markSynced(edit, updates) - verify(noteEditsController).updateElementIds(any()) - verify(mapDataController).updateAll(updates) + verifyInvokedExactlyOnce { singleUploader.upload(eq(edit), any()) } + verifyInvokedExactlyOnce { listener.onUploaded(any(), any()) } + verifyInvokedExactlyOnce { elementEditsController.markSynced(edit, updates) } + verifyInvokedExactlyOnce { noteEditsController.updateElementIds(any()) } + verifyInvokedExactlyOnce { mapDataController.updateAll(updates) } - verify(statisticsController).addOne(any(), any()) + verifyInvokedExactlyOnce { statisticsController.addOne(any(), any()) } } @Test fun `upload catches conflict exception`() = runBlocking { // edit modifies node 1 and way 1 val node1 = node() - val action: ElementEditAction = mock() - on(action.elementKeys).thenReturn(listOf( + val action: ElementEditAction = mock(classOf()) + every { action.elementKeys }.returns(listOf( ElementKey(ElementType.NODE, 1), ElementKey(ElementType.WAY, 1), )) val edit = edit(action = action) // ...but way 1 is gone - on(mapDataApi.getNode(1)).thenReturn(node1) - on(mapDataApi.getWayComplete(1)).thenReturn(null) + every { mapDataApi.getNode(1) }.returns(node1) + every { mapDataApi.getWayComplete(1) }.returns(null) // the edit is the first in the upload queue and the uploader throws a conflict exception - on(elementEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(singleUploader.upload(any(), any())).thenThrow(ConflictException()) + every { elementEditsController.getOldestUnsynced() }.returnsMany(edit) + every { singleUploader.upload(any(), any()) }.throws(ConflictException()) uploader.upload() - verify(singleUploader).upload(eq(edit), any()) - verify(listener).onDiscarded(any(), any()) + verifyInvokedExactlyOnce { singleUploader.upload(eq(edit), any()) } + verifyInvokedExactlyOnce { listener.onDiscarded(any(), any()) } - verify(elementEditsController).markSyncFailed(edit) - verifyNoInteractions(statisticsController) + verifyInvokedExactlyOnce { elementEditsController.markSyncFailed(edit) } + // verifyNoInteractions(statisticsController) - verify(mapDataController).updateAll(eq(MapDataUpdates( + verifyInvokedExactlyOnce { mapDataController.updateAll(eq(MapDataUpdates( updated = listOf(node1), deleted = listOf(ElementKey(ElementType.WAY, 1)) - ))) + )))} } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManagerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManagerTest.kt index b82683e1e7..ee119b9459 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManagerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/OpenChangesetsManagerTest.kt @@ -6,12 +6,14 @@ import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osm.mapdata.MapDataApi import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType import de.westnordost.streetcomplete.data.quest.TestQuestTypeA -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on +import de.westnordost.streetcomplete.testutils.verifyInvokedExactly +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.util.math.translate -import org.mockito.Mockito.never -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import java.util.Locale import kotlin.test.BeforeTest import kotlin.test.Test @@ -19,71 +21,73 @@ import kotlin.test.assertEquals class OpenChangesetsManagerTest { - private lateinit var questType: OsmElementQuestType<*> - private lateinit var mapDataApi: MapDataApi - private lateinit var openChangesetsDB: OpenChangesetsDao - private lateinit var changesetAutoCloser: ChangesetAutoCloser + @Mock private lateinit var questType: OsmElementQuestType<*> + @Mock private lateinit var mapDataApi: MapDataApi + @Mock private lateinit var openChangesetsDB: OpenChangesetsDao + @Mock private lateinit var changesetAutoCloser: ChangesetAutoCloser private lateinit var manager: OpenChangesetsManager - private lateinit var lastEditTimeStore: LastEditTimeStore + @Mock private lateinit var lastEditTimeStore: LastEditTimeStore @BeforeTest fun setUp() { questType = TestQuestTypeA() - mapDataApi = mock() - openChangesetsDB = mock() - changesetAutoCloser = mock() - lastEditTimeStore = mock() + mapDataApi = mock(classOf()) + openChangesetsDB = mock(classOf()) + changesetAutoCloser = mock(classOf()) + lastEditTimeStore = mock(classOf()) manager = OpenChangesetsManager(mapDataApi, openChangesetsDB, changesetAutoCloser, lastEditTimeStore) } @Test fun `create new changeset if none exists`() { - on(openChangesetsDB.get(any(), any())).thenReturn(null) - on(mapDataApi.openChangeset(any())).thenReturn(123L) + every { openChangesetsDB.get(any(), any()) }.returns(null) + every { mapDataApi.openChangeset(any()) }.returns(123L) assertEquals(123L, manager.getOrCreateChangeset(questType, "my source", LatLon(0.0, 0.0), false)) - verify(mapDataApi).openChangeset(any()) - verify(openChangesetsDB).put(any()) + verifyInvokedExactlyOnce { mapDataApi.openChangeset(any()) } + verifyInvokedExactlyOnce { openChangesetsDB.put(any()) } } @Test fun `reuse changeset if one exists`() { - on(openChangesetsDB.get(questType.name, "source")).thenReturn( + every { openChangesetsDB.get(questType.name, "source") }.returns( OpenChangeset(questType.name, "source", 123, LatLon(0.0, 0.0)) ) assertEquals(123L, manager.getOrCreateChangeset(questType, "source", LatLon(0.0, 0.0), false)) - verify(mapDataApi, never()).openChangeset(any()) + verifyInvokedExactly(0) { mapDataApi.openChangeset(any()) } } @Test fun `reuse changeset if one exists and position is far away but should not create new if too far away`() { val p0 = LatLon(0.0, 0.0) val p1 = p0.translate(5001.0, 0.0) - on(openChangesetsDB.get(questType.name, "source")).thenReturn( + every { openChangesetsDB.get(questType.name, "source") }.returns( OpenChangeset(questType.name, "source", 123, p0) ) assertEquals(123L, manager.getOrCreateChangeset(questType, "source", p1, false)) - verify(mapDataApi, never()).openChangeset(any()) + verifyInvokedExactly(0) { mapDataApi.openChangeset(any()) } } @Test fun `close changeset and create new if one exists and position is far away`() { val p0 = LatLon(0.0, 0.0) val p1 = p0.translate(5001.0, 0.0) - on(openChangesetsDB.get(questType.name, "source")).thenReturn( + every { openChangesetsDB.get(questType.name, "source") }.returns( OpenChangeset(questType.name, "source", 123, p0) ) - on(mapDataApi.openChangeset(any())).thenReturn(124L) + every { mapDataApi.openChangeset(any()) }.returns(124L) + every { openChangesetsDB.delete(questType.name, "source") }.returns(true) assertEquals(124L, manager.getOrCreateChangeset(questType, "source", p1, true)) - verify(mapDataApi).closeChangeset(123L) - verify(mapDataApi).openChangeset(any()) - verify(openChangesetsDB).delete(questType.name, "source") - verify(openChangesetsDB).put(OpenChangeset(questType.name, "source", 124L, p1)) + verifyInvokedExactlyOnce { mapDataApi.closeChangeset(123L) } + verifyInvokedExactlyOnce { mapDataApi.openChangeset(any()) } + verifyInvokedExactlyOnce { openChangesetsDB.delete(questType.name, "source") } + verifyInvokedExactlyOnce { openChangesetsDB.put(OpenChangeset(questType.name, "source", 124L, p1)) } } @Test fun `create correct changeset tags`() { - on(openChangesetsDB.get(any(), any())).thenReturn(null) + every { openChangesetsDB.get(any(), any()) }.returns(null) + every { mapDataApi.openChangeset(any()) }.returns(123L) val locale = Locale.getDefault() Locale.setDefault(Locale("es", "AR")) @@ -91,13 +95,13 @@ class OpenChangesetsManagerTest { Locale.setDefault(locale) - verify(mapDataApi).openChangeset(mapOf( + verifyInvokedExactlyOnce {(mapDataApi).openChangeset(mapOf( "source" to "my source", "created_by" to ApplicationConstants.USER_AGENT, "comment" to "test me", "locale" to "es-AR", "StreetComplete:quest_type" to questType.name - )) - verify(openChangesetsDB).put(any()) + ))} + verifyInvokedExactlyOnce { openChangesetsDB.put(any()) } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDaoTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDaoTest.kt index 637723f34b..f6041d5097 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDaoTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/ElementDaoTest.kt @@ -3,131 +3,147 @@ package de.westnordost.streetcomplete.data.osm.mapdata import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.NODE import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.RELATION import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.WAY -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.rel +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.testutils.way -import org.mockito.Mockito.anyCollection -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class ElementDaoTest { - private lateinit var nodeDao: NodeDao - private lateinit var wayDao: WayDao - private lateinit var relationDao: RelationDao - private lateinit var dao: ElementDao + @Mock private lateinit var nodeDao: NodeDao + @Mock private lateinit var wayDao: WayDao + @Mock private lateinit var relationDao: RelationDao + @Mock private lateinit var dao: ElementDao @BeforeTest fun setUp() { - nodeDao = mock() - wayDao = mock() - relationDao = mock() + nodeDao = mock(classOf()) + wayDao = mock(classOf()) + relationDao = mock(classOf()) dao = ElementDao(nodeDao, wayDao, relationDao) } @Test fun putNode() { val node = node(1) dao.put(node) - verify(nodeDao).put(node) + verifyInvokedExactlyOnce { nodeDao.put(node) } } @Test fun getNode() { + every { nodeDao.get(1L) }.returns(node()) dao.get(NODE, 1L) - verify(nodeDao).get(1L) + verifyInvokedExactlyOnce { nodeDao.get(1L) } } @Test fun deleteNode() { + every { nodeDao.delete(1L) }.returns(true) dao.delete(NODE, 1L) - verify(nodeDao).delete(1L) + verifyInvokedExactlyOnce { nodeDao.delete(1L) } } @Test fun putWay() { val way = way() dao.put(way) - verify(wayDao).put(way) + verifyInvokedExactlyOnce { wayDao.put(way) } } @Test fun getWay() { + every { wayDao.get(1L) }.returns(way()) dao.get(WAY, 1L) - verify(wayDao).get(1L) + verifyInvokedExactlyOnce { wayDao.get(1L) } } @Test fun deleteWay() { + every { wayDao.delete(1L) }.returns(true) dao.delete(WAY, 1L) - verify(wayDao).delete(1L) + verifyInvokedExactlyOnce { wayDao.delete(1L) } } @Test fun putRelation() { val relation = rel() dao.put(relation) - verify(relationDao).put(relation) + verifyInvokedExactlyOnce { relationDao.put(relation) } } @Test fun getRelation() { + every { relationDao.get(1L) }.returns(rel()) dao.get(RELATION, 1L) - verify(relationDao).get(1L) + verifyInvokedExactlyOnce { relationDao.get(1L) } } @Test fun deleteRelation() { + every { relationDao.delete(1L) }.returns(true) dao.delete(RELATION, 1L) - verify(relationDao).delete(1L) + verifyInvokedExactlyOnce { relationDao.delete(1L) } } @Test fun putAllRelations() { dao.putAll(listOf(rel())) - verify(relationDao).putAll(anyCollection()) + verifyInvokedExactlyOnce { relationDao.putAll(any()) } } @Test fun putAllWays() { dao.putAll(listOf(way())) - verify(wayDao).putAll(anyCollection()) + verifyInvokedExactlyOnce { wayDao.putAll(any()) } } @Test fun putAllNodes() { dao.putAll(listOf(node())) - verify(nodeDao).putAll(anyCollection()) + verifyInvokedExactlyOnce { nodeDao.putAll(any()) } } @Test fun putAllElements() { dao.putAll(listOf(node(), way(), rel())) - verify(nodeDao).putAll(anyCollection()) - verify(wayDao).putAll(anyCollection()) - verify(relationDao).putAll(anyCollection()) + verifyInvokedExactlyOnce { nodeDao.putAll(any()) } + verifyInvokedExactlyOnce { wayDao.putAll(any()) } + verifyInvokedExactlyOnce { relationDao.putAll(any()) } } @Test fun deleteAllElements() { + every { nodeDao.deleteAll(listOf(0L)) }.returns(1) + every { wayDao.deleteAll(listOf(0L)) }.returns(1) + every { relationDao.deleteAll(listOf(0L)) }.returns(1) + dao.deleteAll(listOf( ElementKey(NODE, 0), ElementKey(WAY, 0), ElementKey(RELATION, 0) )) - verify(nodeDao).deleteAll(listOf(0L)) - verify(wayDao).deleteAll(listOf(0L)) - verify(relationDao).deleteAll(listOf(0L)) + verifyInvokedExactlyOnce { nodeDao.deleteAll(listOf(0L)) } + verifyInvokedExactlyOnce { wayDao.deleteAll(listOf(0L)) } + verifyInvokedExactlyOnce { relationDao.deleteAll(listOf(0L)) } } @Test fun clear() { dao.clear() - verify(nodeDao).clear() - verify(wayDao).clear() - verify(relationDao).clear() + verifyInvokedExactlyOnce { nodeDao.clear() } + verifyInvokedExactlyOnce { wayDao.clear() } + verifyInvokedExactlyOnce { relationDao.clear() } } @Test fun getAllElements() { + every { nodeDao.getAll(listOf(0L)) }.returns(listOf(node())) + every { wayDao.getAll(listOf(0L)) }.returns(listOf(way())) + every { relationDao.getAll(listOf(0L)) }.returns(listOf(rel())) + dao.getAll(listOf( ElementKey(NODE, 0), ElementKey(WAY, 0), ElementKey(RELATION, 0) )) - verify(nodeDao).getAll(listOf(0L)) - verify(wayDao).getAll(listOf(0L)) - verify(relationDao).getAll(listOf(0L)) + verifyInvokedExactlyOnce { nodeDao.getAll(listOf(0L)) } + verifyInvokedExactlyOnce { wayDao.getAll(listOf(0L)) } + verifyInvokedExactlyOnce { relationDao.getAll(listOf(0L)) } } @Test fun getAllElementsByBbox() { @@ -138,13 +154,14 @@ class ElementDaoTest { val wayIds = ways.map { it.id } val relations = listOf(rel(1)) - on(nodeDao.getAll(bbox)).thenReturn(nodes) - on(wayDao.getAllForNodes(eq(nodeIds.toSet()))).thenReturn(ways) - on(relationDao.getAllForElements( + every { nodeDao.getAll(bbox) }.returns(nodes) + every { nodeDao.getAll(eq(listOf())) }.returns(listOf()) + every { wayDao.getAllForNodes(eq(nodeIds.toSet())) }.returns(ways) + every { relationDao.getAllForElements( nodeIds = eq(nodeIds), wayIds = eq(wayIds), relationIds = eq(emptyList()) - )).thenReturn(relations) + )}.returns(relations) assertEquals( nodes + ways + relations, dao.getAll(bbox) @@ -161,14 +178,14 @@ class ElementDaoTest { val wayIds = ways.map { it.id } val relations = listOf(rel(1)) - on(nodeDao.getAll(bbox)).thenReturn(bboxNodes) - on(nodeDao.getAll(outsideBboxNodeIds)).thenReturn(outsideBboxNodes) - on(wayDao.getAllForNodes(eq(bboxNodeIds.toSet()))).thenReturn(ways) - on(relationDao.getAllForElements( + every { nodeDao.getAll(bbox) }.returns(bboxNodes) + every { nodeDao.getAll(outsideBboxNodeIds) }.returns(outsideBboxNodes) + every { wayDao.getAllForNodes(eq(bboxNodeIds.toSet())) }.returns(ways) + every { relationDao.getAllForElements( nodeIds = eq(outsideBboxNodeIds + bboxNodeIds), wayIds = eq(wayIds), relationIds = eq(emptyList()) - )).thenReturn(relations) + )}.returns(relations) assertEquals( bboxNodes + outsideBboxNodes + ways + relations, dao.getAll(bbox) @@ -181,13 +198,13 @@ class ElementDaoTest { val wayIds = listOf(1, 2) val relationIds = listOf(1) - on(nodeDao.getAllIds(bbox)).thenReturn(nodeIds) - on(wayDao.getAllIdsForNodes(eq(nodeIds))).thenReturn(wayIds) - on(relationDao.getAllIdsForElements( + every { nodeDao.getAllIds(bbox) }.returns(nodeIds) + every { wayDao.getAllIdsForNodes(eq(nodeIds)) }.returns(wayIds) + every { relationDao.getAllIdsForElements( nodeIds = eq(nodeIds), wayIds = eq(wayIds), relationIds = eq(emptyList()) - )).thenReturn(relationIds) + )}.returns(relationIds) assertEquals( nodeIds.map { ElementKey(NODE, it) } + wayIds.map { ElementKey(WAY, it) } + diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataCacheTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataCacheTest.kt index 10b7b6b23f..5714a0c3d7 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataCacheTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataCacheTest.kt @@ -5,24 +5,35 @@ import de.westnordost.streetcomplete.data.download.tiles.asBoundingBoxOfEnclosin import de.westnordost.streetcomplete.data.download.tiles.enclosingTilePos import de.westnordost.streetcomplete.data.download.tiles.enclosingTilesRect import de.westnordost.streetcomplete.data.osm.geometry.* -import de.westnordost.streetcomplete.testutils.* +import de.westnordost.streetcomplete.testutils.node +import de.westnordost.streetcomplete.testutils.p +import de.westnordost.streetcomplete.testutils.rel +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder import de.westnordost.streetcomplete.util.math.enclosingBoundingBox import de.westnordost.streetcomplete.util.math.isCompletelyInside -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions -import org.mockito.Mockito.verifyNoMoreInteractions +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.* import kotlin.test.Test internal class MapDataCacheTest { + @Mock val elementDB: ElementDao = mock(classOf()) + @Mock val geometryDB: ElementGeometryDao = mock(classOf()) + @Mock val relationDB: RelationDao = mock(classOf()) + @Mock val nodeDao: NodeDao = mock(classOf()) + @Mock val wayDB: WayDao = mock(classOf()) + @Test fun `update puts way`() { val way = way(1) val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(way)) - val elementDB: ElementDao = mock() - on(elementDB.get(ElementType.WAY, 1L)).thenThrow(IllegalStateException()) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.get(ElementType.WAY, 1L) }.throws(IllegalStateException()) assertEquals(way, cache.getElement(ElementType.WAY, 1L) { type, id -> elementDB.get(type, id) }) } @@ -30,8 +41,8 @@ internal class MapDataCacheTest { val relation = rel(1) val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(relation)) - val elementDB: ElementDao = mock() - on(elementDB.get(ElementType.RELATION, 1L)).thenThrow(IllegalStateException()) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.get(ElementType.RELATION, 1L) }.throws(IllegalStateException()) assertEquals(relation, cache.getElement(ElementType.RELATION, 1L) { type, id -> elementDB.get(type, id) }) } @@ -41,8 +52,8 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedGeometries = listOf(ElementGeometryEntry(ElementType.WAY, 1, geo))) - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.get(ElementType.WAY, 1L)).thenThrow(IllegalStateException()) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.get(ElementType.WAY, 1L) }.throws(IllegalStateException()) assertEquals(geo, cache.getGeometry(ElementType.WAY, 1L) { type, id -> geometryDB.get(type, id) }) } @@ -52,34 +63,33 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedGeometries = listOf(ElementGeometryEntry(ElementType.RELATION, 1, geo))) - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.get(ElementType.RELATION, 1L)).thenThrow(IllegalStateException()) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.get(ElementType.RELATION, 1L) }.throws(IllegalStateException()) assertEquals(geo, cache.getGeometry(ElementType.RELATION, 1L) { type, id -> geometryDB.get(type, id) }) } @Test fun `getElement also caches node if not in spatialCache`() { val node = node(1) val cache = getEmptyMapDataCache() - val elementDB: ElementDao = mock() - on(elementDB.get(ElementType.NODE, 1L)).thenReturn(node).thenReturn(null) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.get(ElementType.NODE, 1L) }.returnsMany(node) assertEquals(node, cache.getElement(ElementType.NODE, 1L) { type, id -> elementDB.get(type, id) }) - verify(elementDB).get(ElementType.NODE, 1L) + verifyInvokedExactlyOnce { elementDB.get(ElementType.NODE, 1L) } // getting a second time does not fetches again - val elementDB2: ElementDao = mock() - on(elementDB2.get(ElementType.NODE, 1L)).thenReturn(node).thenReturn(null) + val elementDB2: ElementDao = mock(classOf()) + every { elementDB2.get(ElementType.NODE, 1L) }.returnsMany(node) assertEquals(node, cache.getElement(ElementType.NODE, 1L) { type, id -> elementDB2.get(type, id) }) - verifyNoInteractions(elementDB2) } @Test fun `getElement fetches and caches way`() { val way = way(2L) val cache = getEmptyMapDataCache() - val elementDB: ElementDao = mock() - on(elementDB.get(ElementType.WAY, 2L)).thenReturn(way).thenThrow(IllegalStateException()) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.get(ElementType.WAY, 2L) }.returnsMany(way) // get way 2 and verify the fetch function is called, but only once assertEquals(way, cache.getElement(ElementType.WAY, 2L) { type, id -> elementDB.get(type, id) }) - verify(elementDB).get(ElementType.WAY, 2L) + verifyInvokedExactlyOnce { elementDB.get(ElementType.WAY, 2L) } // getting a second time does not fetch again assertEquals(way, cache.getElement(ElementType.WAY, 2L) { type, id -> elementDB.get(type, id) }) @@ -88,11 +98,11 @@ internal class MapDataCacheTest { @Test fun `getElement fetches and caches relation`() { val rel = rel(1L) val cache = getEmptyMapDataCache() - val elementDB: ElementDao = mock() - on(elementDB.get(ElementType.RELATION, 1L)).thenReturn(rel).thenThrow(IllegalStateException()) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.get(ElementType.RELATION, 1L) }.returnsMany(rel) // get rel 1 and verify the fetch function is called, but only once assertEquals(rel, cache.getElement(ElementType.RELATION, 1L) { type, id -> elementDB.get(type, id) }) - verify(elementDB).get(ElementType.RELATION, 1L) + verifyInvokedExactlyOnce { elementDB.get(ElementType.RELATION, 1L) } // getting a second time does not fetch again assertEquals(rel, cache.getElement(ElementType.RELATION, 1L) { type, id -> elementDB.get(type, id) }) @@ -103,17 +113,17 @@ internal class MapDataCacheTest { val geo = ElementPointGeometry(p) val cache = getEmptyMapDataCache() - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.get(ElementType.NODE, 2L)).thenReturn(geo).thenThrow(IllegalStateException()) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.get(ElementType.NODE, 2L) }.returnsMany(geo) // get node 2 and verify the fetch function is called, but only once assertEquals(geo, cache.getGeometry(ElementType.NODE, 2L) { type, id -> geometryDB.get(type, id) }) - verify(geometryDB).get(ElementType.NODE, 2L) + verifyInvokedExactlyOnce { geometryDB.get(ElementType.NODE, 2L) } // getting a second time fetches again - val geometryDB2: ElementGeometryDao = mock() - on(geometryDB2.get(ElementType.NODE, 2L)).thenReturn(geo).thenThrow(IllegalStateException()) + val geometryDB2: ElementGeometryDao = mock(classOf()) + every { geometryDB2.get(ElementType.NODE, 2L) }.returnsMany(geo) assertEquals(geo, cache.getGeometry(ElementType.NODE, 2L) { type, id -> geometryDB2.get(type, id) }) - verify(geometryDB2).get(ElementType.NODE, 2L) + verifyInvokedExactlyOnce { geometryDB2.get(ElementType.NODE, 2L) } } @Test fun `getGeometry fetches and caches way geometry`() { @@ -121,11 +131,11 @@ internal class MapDataCacheTest { val geo = ElementPolylinesGeometry(listOf(listOf(p)), p) val cache = getEmptyMapDataCache() - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.get(ElementType.WAY, 2L)).thenReturn(geo).thenThrow(IllegalStateException()) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.get(ElementType.WAY, 2L) }.returnsMany(geo) // get geo and verify the fetch function is called, but only once assertEquals(geo, cache.getGeometry(ElementType.WAY, 2L) { type, id -> geometryDB.get(type, id) }) - verify(geometryDB).get(ElementType.WAY, 2L) + verifyInvokedExactlyOnce { geometryDB.get(ElementType.WAY, 2L) } // getting a second time does not fetch again assertEquals(geo, cache.getGeometry(ElementType.WAY, 2L) { type, id -> geometryDB.get(type, id) }) @@ -136,11 +146,11 @@ internal class MapDataCacheTest { val geo = ElementPolygonsGeometry(listOf(listOf(p)), p) val cache = getEmptyMapDataCache() - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.get(ElementType.RELATION, 2L)).thenReturn(geo).thenThrow(IllegalStateException()) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.get(ElementType.RELATION, 2L) }.returnsMany(geo) // get way 2 and verify the fetch function is called, but only once assertEquals(geo, cache.getGeometry(ElementType.RELATION, 2L) { type, id -> geometryDB.get(type, id) }) - verify(geometryDB).get(ElementType.RELATION, 2L) + verifyInvokedExactlyOnce { geometryDB.get(ElementType.RELATION, 2L) } // getting a second time does not fetch again assertEquals(geo, cache.getGeometry(ElementType.RELATION, 2L) { type, id -> geometryDB.get(type, id) }) @@ -174,10 +184,10 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedElements = nodes, bbox = nodesRect.asBoundingBox(16)) - val nodeDB: NodeDao = mock() - on(nodeDB.getAll(listOf(uncachedNode.id))).thenReturn(listOf(uncachedNode)) + val nodeDB: NodeDao = mock(classOf()) + every { nodeDB.getAll(listOf(uncachedNode.id)) }.returns(listOf(uncachedNode)) assertTrue(cache.getNodes(allNodes.map { it.id }) { nodeDB.getAll(it) }.containsExactlyInAnyOrder(allNodes)) - verify(nodeDB).getAll(listOf(uncachedNode.id)) + verifyInvokedExactlyOnce { nodeDB.getAll(listOf(uncachedNode.id)) } } @Test fun `getNodes does not fetch cached nodes`() { @@ -190,9 +200,9 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedElements = nodes) - val nodeDB: NodeDao = mock() + val nodeDB: NodeDao = mock(classOf()) assertTrue(cache.getNodes(nodes.map { it.id }) { nodeDB.getAll(it) }.containsExactlyInAnyOrder(nodes)) - verifyNoInteractions(nodeDB) + // verifyNoInteractions(nodeDB) } @Test fun `getNodes fetches formerly cached nodes outside spatial cache after trim`() { @@ -206,11 +216,11 @@ internal class MapDataCacheTest { cache.update(updatedElements = nodes) cache.trim(4) - val nodeDB: NodeDao = mock() + val nodeDB: NodeDao = mock(classOf()) val nodeIds = nodes.map { it.id }.sorted() // use sorted to avoid issues with order - on(nodeDB.getAll(nodeIds)).thenReturn(nodes) + every { nodeDB.getAll(nodeIds) }.returns(nodes) assertTrue(cache.getNodes(nodes.map { it.id }) { nodeDB.getAll(it.sorted()) }.containsExactlyInAnyOrder(nodes)) - verify(nodeDB).getAll(nodeIds) + verifyInvokedExactlyOnce { nodeDB.getAll(nodeIds) } } @Test fun `getElements doesn't fetch cached elements`() { @@ -243,15 +253,15 @@ internal class MapDataCacheTest { cache.update(updatedElements = cachedElements) val keysNotInCache = elements.filterNot { it in cachedElements }.map { ElementKey(it.type, it.id) }.toHashSet() - val elementDB: ElementDao = mock() - on(elementDB.getAll(keysNotInCache)).thenReturn(elements.filterNot { it in cachedElements }) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.getAll(keysNotInCache) }.returns(elements.filterNot { it in cachedElements }) assertTrue(cache.getElements(elements.map { ElementKey(it.type, it.id) }) { elementDB.getAll(it.toHashSet()) }.containsExactlyInAnyOrder(elements)) - verify(elementDB).getAll(keysNotInCache) + verifyInvokedExactlyOnce { elementDB.getAll(keysNotInCache) } // check whether elements are cached now - on(elementDB.getAll(listOf(ElementKey(node1.type, node1.id)))).thenReturn(listOf(node1)) + every { elementDB.getAll(listOf(ElementKey(node1.type, node1.id))) }.returns(listOf(node1)) assertTrue(cache.getElements(elements.map { ElementKey(it.type, it.id) }) { elementDB.getAll(it) }.containsExactlyInAnyOrder(elements)) - verifyNoMoreInteractions(elementDB) + // verifyNoMoreInteractions(elementDB) } @Test fun `getElements fetches all elements if none are cached`() { @@ -266,15 +276,15 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() val keys = elements.map { ElementKey(it.type, it.id) }.toHashSet() - val elementDB: ElementDao = mock() - on(elementDB.getAll(keys)).thenReturn(elements) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.getAll(keys) }.returns(elements) assertTrue(cache.getElements(elements.map { ElementKey(it.type, it.id) }) { elementDB.getAll(it.toHashSet()) }.containsExactlyInAnyOrder(elements)) - verify(elementDB).getAll(keys) + verifyInvokedExactlyOnce { elementDB.getAll(keys) } // check whether elements are cached now - on(elementDB.getAll(listOf(ElementKey(node1.type, node1.id)))).thenReturn(listOf(node1)) + every { elementDB.getAll(listOf(ElementKey(node1.type, node1.id))) }.returns(listOf(node1)) assertTrue(cache.getElements(elements.map { ElementKey(it.type, it.id) }) { elementDB.getAll(it) }.containsExactlyInAnyOrder(elements)) - verifyNoMoreInteractions(elementDB) + // verifyNoMoreInteractions(elementDB) } @Test fun `getGeometries doesn't fetch cached geometries`() { @@ -311,15 +321,15 @@ internal class MapDataCacheTest { cache.update(updatedGeometries = cachedEntries) val keysNotInCache = entries.filterNot { it in cachedEntries }.map { ElementKey(it.elementType, it.elementId) }.toHashSet() - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.getAllEntries(keysNotInCache)).thenReturn(entries.filterNot { it in cachedEntries }) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.getAllEntries(keysNotInCache) }.returns(entries.filterNot { it in cachedEntries }) assertTrue(cache.getGeometries(entries.map { ElementKey(it.elementType, it.elementId) }) { geometryDB.getAllEntries(it.toHashSet()) }.containsExactlyInAnyOrder(entries)) - verify(geometryDB).getAllEntries(keysNotInCache) + verifyInvokedExactlyOnce { geometryDB.getAllEntries(keysNotInCache) } // check whether geometries except nodeEntry are cached now - on(geometryDB.getAllEntries(listOf(ElementKey(node1.type, node1.id)))).thenReturn(listOf(nodeEntry)) + every { geometryDB.getAllEntries(listOf(ElementKey(node1.type, node1.id))) }.returns(listOf(nodeEntry)) assertTrue(cache.getGeometries(entries.map { ElementKey(it.elementType, it.elementId) }) { geometryDB.getAllEntries(it) }.containsExactlyInAnyOrder(entries)) - verify(geometryDB).getAllEntries(listOf(ElementKey(node1.type, node1.id))) + verifyInvokedExactlyOnce { geometryDB.getAllEntries(listOf(ElementKey(node1.type, node1.id))) } } @Test fun `getGeometries fetches all geometries if none are cached`() { @@ -336,15 +346,15 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() val keys = entries.map { ElementKey(it.elementType, it.elementId) }.toHashSet() - val geometryDB: ElementGeometryDao = mock() - on(geometryDB.getAllEntries(keys)).thenReturn(entries) + val geometryDB: ElementGeometryDao = mock(classOf()) + every { geometryDB.getAllEntries(keys) }.returns(entries) assertTrue(cache.getGeometries(entries.map { ElementKey(it.elementType, it.elementId) }) { geometryDB.getAllEntries(it.toHashSet()) }.containsExactlyInAnyOrder(entries)) - verify(geometryDB).getAllEntries(keys) + verifyInvokedExactlyOnce { geometryDB.getAllEntries(keys) } // check whether geometries except nodeEntry are cached now - on(geometryDB.getAllEntries(listOf(ElementKey(node1.type, node1.id)))).thenReturn(listOf(nodeEntry)) + every { geometryDB.getAllEntries(listOf(ElementKey(node1.type, node1.id))) }.returns(listOf(nodeEntry)) assertTrue(cache.getGeometries(entries.map { ElementKey(it.elementType, it.elementId) }) { geometryDB.getAllEntries(it) }.containsExactlyInAnyOrder(entries)) - verify(geometryDB).getAllEntries(listOf(ElementKey(node1.type, node1.id))) + verifyInvokedExactlyOnce { geometryDB.getAllEntries(listOf(ElementKey(node1.type, node1.id))) } } @Test fun `update removes elements`() { @@ -373,11 +383,11 @@ internal class MapDataCacheTest { val way3 = way(3, nodes = listOf(3L, 2L)) val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(way1, way2, way3)) - val wayDB: WayDao = mock() - on(wayDB.getAllForNode(1L)).thenReturn(listOf(way1, way2)).thenThrow(IllegalStateException()) + val wayDB: WayDao = mock(classOf()) + every { wayDB.getAllForNode(1L) }.returnsMany(listOf(way1, way2)) // fetches from cache if we didn't put the node assertTrue(cache.getWaysForNode(1L) { wayDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(way1, way2))) - verify(wayDB).getAllForNode(1L) + verifyInvokedExactlyOnce { wayDB.getAllForNode(1L) } // now we have it cached assertTrue(cache.getWaysForNode(1L) { emptyList() }.containsExactlyInAnyOrder(listOf(way1, way2))) } @@ -668,14 +678,14 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(way1)) - val wayDB: WayDao = mock() - on(wayDB.getAllForNode(1L)).thenReturn(listOf(way1)) + val wayDB: WayDao = mock(classOf()) + every { wayDB.getAllForNode(1L) }.returns(listOf(way1)) assertTrue(cache.getWaysForNode(1L) { wayDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(way1))) - verify(wayDB).getAllForNode(1L) // was fetched from cache + verifyInvokedExactlyOnce { wayDB.getAllForNode(1L) } // was fetched from cache - on(wayDB.getAllForNode(2L)).thenReturn(listOf(way1)) + every { wayDB.getAllForNode(2L) }.returns(listOf(way1)) assertTrue(cache.getWaysForNode(2L) { wayDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(way1))) - verify(wayDB).getAllForNode(2L) // was fetched from cache + verifyInvokedExactlyOnce { wayDB.getAllForNode(2L) } // was fetched from cache } @Test fun `update does create waysByNodeId entry if node is in spatialCache`() { @@ -701,18 +711,18 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(way1)) - val wayDB: WayDao = mock() - on(wayDB.getAllForNode(1L)).thenReturn(listOf(way1)) + val wayDB: WayDao = mock(classOf()) + every { wayDB.getAllForNode(1L) }.returns(listOf(way1)) assertTrue(cache.getWaysForNode(1L) { wayDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(way1))) - verify(wayDB).getAllForNode(1L) // was fetched from cache + verifyInvokedExactlyOnce { wayDB.getAllForNode(1L) } // was fetched from cache val way2 = way(2, nodes = listOf(1L, 2L)) cache.update(updatedElements = listOf(way2)) assertTrue(cache.getWaysForNode(1L) { throw IllegalStateException() }.containsExactlyInAnyOrder(listOf(way1, way2))) - on(wayDB.getAllForNode(2L)).thenReturn(listOf(way1, way2)) + every { wayDB.getAllForNode(2L) }.returns(listOf(way1, way2)) assertTrue(cache.getWaysForNode(2L) { wayDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(way1, way2))) - verify(wayDB).getAllForNode(2L) // was fetched from cache + verifyInvokedExactlyOnce { wayDB.getAllForNode(2L) } // was fetched from cache } @Test fun `update doesn't create relationsByElementKey entry if element is not referenced by spatialCache`() { @@ -723,13 +733,13 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(node1, node2, way1, rel1)) - val relationDB: RelationDao = mock() - on(relationDB.getAllForNode(2L)).thenReturn(listOf(rel1)) - on(relationDB.getAllForWay(1L)).thenReturn(listOf(rel1)) + val relationDB: RelationDao = mock(classOf()) + every { relationDB.getAllForNode(2L) }.returns(listOf(rel1)) + every { relationDB.getAllForWay(1L) }.returns(listOf(rel1)) assertTrue(cache.getRelationsForNode(2L) { relationDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(rel1))) - verify(relationDB).getAllForNode(2L) // was fetched from cache + verifyInvokedExactlyOnce { relationDB.getAllForNode(2L) } // was fetched from cache assertTrue(cache.getRelationsForWay(1L) { relationDB.getAllForWay(it) }.containsExactlyInAnyOrder(listOf(rel1))) - verify(relationDB).getAllForWay(1L) // was fetched from cache + verifyInvokedExactlyOnce { relationDB.getAllForWay(1L) } // was fetched from cache } @Test fun `update does create relationsByElementKey entry if element is referenced by spatialCache`() { @@ -756,18 +766,18 @@ internal class MapDataCacheTest { val cache = getEmptyMapDataCache() cache.update(updatedElements = listOf(rel1)) - val relationDB: RelationDao = mock() - on(relationDB.getAllForWay(1L)).thenReturn(listOf(rel1)) + val relationDB: RelationDao = mock(classOf()) + every { relationDB.getAllForWay(1L) }.returns(listOf(rel1)) assertTrue(cache.getRelationsForWay(1L) { relationDB.getAllForWay(it) }.containsExactlyInAnyOrder(listOf(rel1))) - verify(relationDB).getAllForWay(1L) // was fetched from cache + verifyInvokedExactlyOnce { relationDB.getAllForWay(1L) } // was fetched from cache val rel2 = rel(2, members = listOf(RelationMember(ElementType.NODE, 3L, ""), RelationMember(ElementType.WAY, 1L, ""))) cache.update(updatedElements = listOf(rel2)) assertTrue(cache.getRelationsForWay(1L) { throw IllegalStateException() }.containsExactlyInAnyOrder(listOf(rel1, rel2))) - on(relationDB.getAllForNode(3L)).thenReturn(listOf(rel2)) + every { relationDB.getAllForNode(3L) }.returns(listOf(rel2)) assertTrue(cache.getRelationsForNode(3L) { relationDB.getAllForNode(it) }.containsExactlyInAnyOrder(listOf(rel2))) - verify(relationDB).getAllForNode(3L) // was fetched from cache + verifyInvokedExactlyOnce { relationDB.getAllForNode(3L) } // was fetched from cache } @Test fun `getMapDataWithGeometry fetches all and caches if nothing is cached`() { @@ -783,8 +793,8 @@ internal class MapDataCacheTest { val rel1 = rel(1, members = listOf(RelationMember(ElementType.NODE, 1L, ""))) val rel2 = rel(2, members = listOf(RelationMember(ElementType.WAY, 1L, ""))) val elementsInsideBBox = listOf(node1, node2, node3, way1, way2, way3, rel1, rel2) - val elementDB: ElementDao = mock() - on(elementDB.getAll(nodesRect.asBoundingBox(16))).thenReturn(elementsInsideBBox) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.getAll(nodesRect.asBoundingBox(16)) }.returns(elementsInsideBBox) val cache = MapDataCache(16, 4, 10, { elementDB.getAll(it) to emptyList() }, { emptyList() }) val expectedMapData = MutableMapDataWithGeometry().apply { @@ -793,10 +803,10 @@ internal class MapDataCacheTest { boundingBox = nodesBBox } assertEquals(expectedMapData, cache.getMapDataWithGeometry(nodesBBox)) - verify(elementDB).getAll(nodesRect.asBoundingBox(16)) + verifyInvokedExactlyOnce { elementDB.getAll(nodesRect.asBoundingBox(16)) } // second time it's cached assertEquals(expectedMapData, cache.getMapDataWithGeometry(nodesBBox)) - verifyNoMoreInteractions(elementDB) + // verifyNoMoreInteractions(elementDB) } @Test fun `getMapDataWithGeometry fetches only part if something is already cached`() { @@ -813,8 +823,8 @@ internal class MapDataCacheTest { val way3 = way(3, nodes = listOf(3L, 2L)) val rel1 = rel(1, members = listOf(RelationMember(ElementType.NODE, 1L, ""))) val rel2 = rel(2, members = listOf(RelationMember(ElementType.WAY, 1L, ""))) - val elementDB: ElementDao = mock() - on(elementDB.getAll(node1rect.asBoundingBox(16))).thenReturn(listOf(node1, node3, way2, way3, rel1)) + val elementDB: ElementDao = mock(classOf()) + every { elementDB.getAll(node1rect.asBoundingBox(16)) }.returns(listOf(node1, node3, way2, way3, rel1)) val cache = MapDataCache(16, 4, 10, { elementDB.getAll(it) to emptyList() }, { emptyList() }) cache.update(updatedElements = listOf(node2, way1, rel2), bbox = node2rect.asBoundingBox(16)) @@ -825,10 +835,9 @@ internal class MapDataCacheTest { boundingBox = nodesBBox } assertEquals(expectedMapData, cache.getMapDataWithGeometry(nodesBBox)) - verify(elementDB).getAll(node1rect.asBoundingBox(16)) + verifyInvokedExactlyOnce { elementDB.getAll(node1rect.asBoundingBox(16)) } // second time it's cached assertEquals(expectedMapData, cache.getMapDataWithGeometry(nodesBBox)) - verifyNoMoreInteractions(elementDB) } @Test fun `getMapDataWithGeometry fetches nothing if all is cached`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataControllerTest.kt index dbbf3142ec..28269d2151 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataControllerTest.kt @@ -8,16 +8,18 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryEntry import de.westnordost.streetcomplete.data.osm.geometry.ElementPointGeometry import de.westnordost.streetcomplete.data.osm.mapdata.ElementType.NODE import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.bbox -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.pGeom +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder -import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.doesNothing +import io.mockative.eq +import io.mockative.every +import io.mockative.mock import java.lang.Thread.sleep import kotlin.test.BeforeTest import kotlin.test.Test @@ -27,42 +29,45 @@ import kotlin.test.assertTrue class MapDataControllerTest { - private lateinit var nodeDB: NodeDao - private lateinit var wayDB: WayDao - private lateinit var relationDB: RelationDao - private lateinit var geometryDB: ElementGeometryDao - private lateinit var elementDB: ElementDao + @Mock private lateinit var nodeDB: NodeDao + @Mock private lateinit var wayDB: WayDao + @Mock private lateinit var relationDB: RelationDao + @Mock private lateinit var geometryDB: ElementGeometryDao + @Mock private lateinit var elementDB: ElementDao private lateinit var controller: MapDataController - private lateinit var geometryCreator: ElementGeometryCreator - private lateinit var createdElementsController: CreatedElementsController + @Mock private lateinit var geometryCreator: ElementGeometryCreator + @Mock private lateinit var createdElementsController: CreatedElementsController + + // dummy + @Mock private lateinit var listener: MapDataController.Listener @BeforeTest fun setUp() { - nodeDB = mock() - wayDB = mock() - relationDB = mock() - geometryDB = mock() - elementDB = mock() - geometryCreator = mock() - createdElementsController = mock() + nodeDB = mock(classOf()) + wayDB = mock(classOf()) + relationDB = mock(classOf()) + geometryDB = mock(classOf()) + elementDB = mock(classOf()) + geometryCreator = mock(classOf()) + createdElementsController = mock(classOf()) controller = MapDataController(nodeDB, wayDB, relationDB, elementDB, geometryDB, geometryCreator, createdElementsController) } @Test fun get() { val node = node(5) - on(elementDB.get(NODE, 5L)).thenReturn(node) + every { elementDB.get(NODE, 5L) }.returns(node) assertEquals(node, controller.get(NODE, 5L)) } @Test fun getGeometry() { val pGeom = pGeom() - on(geometryDB.get(NODE, 5L)).thenReturn(pGeom) + every { geometryDB.get(NODE, 5L) }.returns(pGeom) assertEquals(pGeom, controller.getGeometry(NODE, 5L)) } @Test fun getGeometries() { val pGeom = ElementGeometryEntry(NODE, 1, pGeom()) val keys = listOf(ElementKey(NODE, 1)) - on(geometryDB.getAllEntries(keys)).thenReturn(listOf(pGeom)) + every { geometryDB.getAllEntries(keys) }.returns(listOf(pGeom)) assertEquals( listOf(pGeom), controller.getGeometries(keys) @@ -81,8 +86,9 @@ class MapDataControllerTest { ElementKey(NODE, 2L), ) val elements = listOf(node(1), node(2)) - on(elementDB.getAll(bboxCacheWillRequest)).thenReturn(elements) - on(geometryDB.getAllEntries(elementKeys)).thenReturn(geomEntries) + every { elementDB.getAll(bboxCacheWillRequest) }.returns(elements) + every { geometryDB.getAllEntries(emptyList()) }.returns(emptyList()) + every { geometryDB.getAllEntries(elementKeys) }.returns(geomEntries) val mapData = controller.getMapDataWithGeometry(bbox) assertTrue(mapData.nodes.containsExactlyInAnyOrder(elements)) @@ -105,9 +111,18 @@ class MapDataControllerTest { ElementGeometryEntry(NODE, 1L, pGeom()), ElementGeometryEntry(NODE, 2L, pGeom()), ) - on(geometryCreator.create(any(), any(), anyBoolean())).thenReturn(pGeom()) - val listener = mock() + val expectedDeleteKeys = deleteKeys + idUpdates.map { ElementKey(it.elementType, it.oldElementId) } + + every { geometryCreator.create(any(), any(), any()) }.returns(pGeom()) + + every { geometryDB.deleteAll(expectedDeleteKeys) }.returns(expectedDeleteKeys.size) + every { elementDB.deleteAll(expectedDeleteKeys) }.returns(expectedDeleteKeys.size) + every { elementDB.putAll(elements) }.doesNothing() + every { geometryDB.putAll(eq(geomEntries)) }.doesNothing() + every { createdElementsController.putAll(eq(idUpdates.map { ElementKey(it.elementType, it.newElementId) })) }.doesNothing() + + val listener = mock(classOf()) controller.addListener(listener) controller.updateAll(MapDataUpdates( updated = elements, @@ -115,15 +130,14 @@ class MapDataControllerTest { idUpdates = idUpdates )) - val expectedDeleteKeys = deleteKeys + idUpdates.map { ElementKey(it.elementType, it.oldElementId) } - verify(geometryDB).deleteAll(expectedDeleteKeys) - verify(elementDB).deleteAll(expectedDeleteKeys) - verify(elementDB).putAll(elements) - verify(geometryDB).putAll(eq(geomEntries)) - verify(createdElementsController).putAll(eq(idUpdates.map { ElementKey(it.elementType, it.newElementId) })) + verifyInvokedExactlyOnce { geometryDB.deleteAll(expectedDeleteKeys) } + verifyInvokedExactlyOnce { elementDB.deleteAll(expectedDeleteKeys) } + verifyInvokedExactlyOnce { elementDB.putAll(elements) } + verifyInvokedExactlyOnce { geometryDB.putAll(eq(geomEntries)) } + verifyInvokedExactlyOnce { createdElementsController.putAll(eq(idUpdates.map { ElementKey(it.elementType, it.newElementId) })) } sleep(100) - verify(listener).onUpdated(any(), eq(expectedDeleteKeys)) + verifyInvokedExactlyOnce { listener.onUpdated(any(), eq(expectedDeleteKeys)) } } @Test fun deleteOlderThan() { @@ -143,33 +157,38 @@ class MapDataControllerTest { ElementKey(ElementType.RELATION, 1L), ) val elementKeys = relationKeys + wayKeys + filteredNodeKeys - on(nodeDB.getIdsOlderThan(123L)).thenReturn(nodeKeys.map { it.id }) - on(wayDB.getIdsOlderThan(123L)).thenReturn(wayKeys.map { it.id }) - on(relationDB.getIdsOlderThan(123L)).thenReturn(relationKeys.map { it.id }) - on(wayDB.filterNodeIdsWithoutWays(nodeKeys.map { it.id })).thenReturn(filteredNodeKeys.map { it.id }) - val listener = mock() + every { nodeDB.getIdsOlderThan(123L) }.returns(nodeKeys.map { it.id }) + every { wayDB.getIdsOlderThan(123L) }.returns(wayKeys.map { it.id }) + every { relationDB.getIdsOlderThan(123L) }.returns(relationKeys.map { it.id }) + every { wayDB.filterNodeIdsWithoutWays(nodeKeys.map { it.id }) }.returns(filteredNodeKeys.map { it.id }) + every { elementDB.deleteAll(wayKeys + relationKeys) }.returns((wayKeys + relationKeys).size) + every { elementDB.deleteAll(filteredNodeKeys) }.returns(filteredNodeKeys.size) + every { geometryDB.deleteAll(elementKeys) }.returns(elementKeys.size) + every { createdElementsController.deleteAll(elementKeys) }.doesNothing() + + val listener = mock(classOf()) controller.addListener(listener) controller.deleteOlderThan(123L) - verify(elementDB).deleteAll(wayKeys + relationKeys) - verify(elementDB).deleteAll(filteredNodeKeys) - verify(geometryDB).deleteAll(elementKeys) - verify(createdElementsController).deleteAll(elementKeys) + verifyInvokedExactlyOnce { elementDB.deleteAll(wayKeys + relationKeys) } + verifyInvokedExactlyOnce { elementDB.deleteAll(filteredNodeKeys) } + verifyInvokedExactlyOnce { geometryDB.deleteAll(elementKeys) } + verifyInvokedExactlyOnce { createdElementsController.deleteAll(elementKeys) } sleep(100) - verify(listener).onUpdated(any(), eq(elementKeys)) + verifyInvokedExactlyOnce { listener.onUpdated(any(), eq(elementKeys)) } } @Test fun clear() { - val listener = mock() + val listener = mock(classOf()) controller.addListener(listener) controller.clear() - verify(elementDB).clear() - verify(geometryDB).clear() - verify(createdElementsController).clear() - verify(listener).onCleared() + verifyInvokedExactlyOnce { elementDB.clear() } + verifyInvokedExactlyOnce { geometryDB.clear() } + verifyInvokedExactlyOnce { createdElementsController.clear() } + verifyInvokedExactlyOnce { listener.onCleared() } } @Test fun `putAllForBBox when nothing was there before`() { @@ -186,19 +205,24 @@ class MapDataControllerTest { mapData.nodeGeometriesById[1] = geomEntries[0].geometry as ElementPointGeometry mapData.nodeGeometriesById[2] = geomEntries[1].geometry as ElementPointGeometry - on(elementDB.getAllKeys(bbox)).thenReturn(emptyList()) - on(geometryDB.getAllEntries(emptyList())).thenReturn(emptyList()) - on(geometryCreator.create(any(), any(), anyBoolean())).thenReturn(pGeom()) + every { elementDB.getAllKeys(bbox) }.returns(emptyList()) + every { geometryDB.getAllEntries(emptyList()) }.returns(emptyList()) + every { geometryCreator.create(any(), any(), any()) }.returns(pGeom()) + every { elementDB.deleteAll(eq(emptySet())) }.returns(0) + every { geometryDB.deleteAll(eq(emptySet())) }.returns(0) + every { geometryDB.putAll(eq(geomEntries)) }.doesNothing() + every { elementDB.putAll(eq(mapData)) }.doesNothing() - val listener = mock() + val listener = mock(classOf()) + every { listener.onReplacedForBBox(eq(bbox), any()) }.doesNothing() controller.addListener(listener) controller.putAllForBBox(bbox, mapData) - verify(elementDB).deleteAll(eq(emptySet())) - verify(geometryDB).deleteAll(eq(emptySet())) - verify(geometryDB).putAll(eq(geomEntries)) - verify(elementDB).putAll(eq(mapData)) - verify(listener).onReplacedForBBox(eq(bbox), any()) + verifyInvokedExactlyOnce { elementDB.deleteAll(eq(emptySet())) } + verifyInvokedExactlyOnce { geometryDB.deleteAll(eq(emptySet())) } + verifyInvokedExactlyOnce { geometryDB.putAll(eq(geomEntries)) } + verifyInvokedExactlyOnce { elementDB.putAll(eq(mapData)) } + verifyInvokedExactlyOnce { listener.onReplacedForBBox(eq(bbox), any()) } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestControllerTest.kt index c7dcc39250..cc2d070942 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestControllerTest.kt @@ -17,20 +17,22 @@ import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.data.quest.OsmQuestKey import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry import de.westnordost.streetcomplete.data.quest.TestQuestTypeA -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.argThat import de.westnordost.streetcomplete.testutils.bbox -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node import de.westnordost.streetcomplete.testutils.note -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.osmQuest import de.westnordost.streetcomplete.testutils.osmQuestKey import de.westnordost.streetcomplete.testutils.p import de.westnordost.streetcomplete.testutils.pGeom +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.matches +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -38,27 +40,28 @@ import kotlin.test.assertTrue class OsmQuestControllerTest { - private lateinit var db: OsmQuestDao - private lateinit var hiddenDB: OsmQuestsHiddenDao - private lateinit var mapDataSource: MapDataWithEditsSource - private lateinit var notesSource: NotesWithEditsSource + @Mock private lateinit var db: OsmQuestDao + @Mock private lateinit var hiddenDB: OsmQuestsHiddenDao + @Mock private lateinit var mapDataSource: MapDataWithEditsSource + @Mock private lateinit var notesSource: NotesWithEditsSource private lateinit var questTypeRegistry: QuestTypeRegistry + // todo private lateinit var countryBoundaries: CountryBoundaries private lateinit var ctrl: OsmQuestController - private lateinit var listener: OsmQuestSource.Listener - private lateinit var hideListener: OsmQuestsHiddenSource.Listener + @Mock private lateinit var listener: OsmQuestSource.Listener + @Mock private lateinit var hideListener: OsmQuestsHiddenSource.Listener private lateinit var mapDataListener: MapDataWithEditsSource.Listener private lateinit var notesListener: NotesWithEditsSource.Listener @BeforeTest fun setUp() { - db = mock() + db = mock(classOf()) - hiddenDB = mock() - mapDataSource = mock() + hiddenDB = mock(classOf()) + mapDataSource = mock(classOf()) - notesSource = mock() + notesSource = mock(classOf()) questTypeRegistry = QuestTypeRegistry(listOf( 0 to ApplicableQuestType, 1 to NotApplicableQuestType, @@ -66,20 +69,20 @@ class OsmQuestControllerTest { 3 to ApplicableQuestTypeNotInAnyCountry, 4 to ApplicableQuestType2 )) - countryBoundaries = mock() + countryBoundaries = mock(classOf()) - on(mapDataSource.addListener(any())).then { invocation -> - mapDataListener = invocation.getArgument(0) + every { mapDataSource.addListener(any()) }.invokes { arguments -> + mapDataListener = arguments[0] as MapDataWithEditsSource.Listener Unit } - on(notesSource.addListener(any())).then { invocation -> - notesListener = invocation.getArgument(0) + every { notesSource.addListener(any()) }.invokes { arguments -> + notesListener = arguments[0] as NotesWithEditsSource.Listener Unit } - listener = mock() - hideListener = mock() + listener = mock(classOf()) + hideListener = mock(classOf()) ctrl = OsmQuestController(db, hiddenDB, mapDataSource, notesSource, questTypeRegistry, lazyOf(countryBoundaries)) ctrl.addListener(listener) ctrl.addListener(hideListener) @@ -90,8 +93,8 @@ class OsmQuestControllerTest { val entry = questEntry(NODE, 1, "ApplicableQuestType") val g = pGeom() - on(db.get(osmQuestKey(NODE, 1, "ApplicableQuestType"))).thenReturn(entry) - on(mapDataSource.getGeometry(NODE, 1)).thenReturn(g) + every { db.get(osmQuestKey(NODE, 1, "ApplicableQuestType")) }.returns(entry) + every { mapDataSource.getGeometry(NODE, 1) }.returns(g) val expectedQuest = OsmQuest(ApplicableQuestType, NODE, 1, g) assertEquals(expectedQuest, ctrl.getVisible(key)) @@ -113,15 +116,15 @@ class OsmQuestControllerTest { val hiddenQuests = listOf(OsmQuestKey(NODE, 2, "ApplicableQuestType")) val bbox = bbox() - on(hiddenDB.getAllIds()).thenReturn(hiddenQuests) - on(notesSource.getAllPositions(any())).thenReturn(listOf(notePos)) - on(db.getAllInBBox(bbox, null)).thenReturn(entries) - on(mapDataSource.getGeometries(argThat { + every { hiddenDB.getAllIds() }.returns(hiddenQuests) + every { notesSource.getAllPositions(any()) }.returns(listOf(notePos)) + every { db.getAllInBBox(bbox, null) }.returns(entries) + every { mapDataSource.getGeometries(matches { it.containsExactlyInAnyOrder(listOf( ElementKey(NODE, 1), ElementKey(NODE, 4), )) - })).thenReturn(listOf( + })}.returns(listOf( ElementGeometryEntry(NODE, 1, geoms[0]) )) @@ -138,7 +141,7 @@ class OsmQuestControllerTest { ElementPointGeometry(p()), ) - on(hiddenDB.getNewerThan(123L)).thenReturn(listOf( + every { hiddenDB.getNewerThan(123L) }.returns(listOf( // ok! OsmQuestKeyWithTimestamp(OsmQuestKey(NODE, 1L, "ApplicableQuestType"), 250), // unknown quest type @@ -146,13 +149,13 @@ class OsmQuestControllerTest { // no geometry! OsmQuestKeyWithTimestamp(OsmQuestKey(NODE, 3L, "ApplicableQuestType"), 250), )) - on(mapDataSource.getGeometries(argThat { + every { mapDataSource.getGeometries(matches { it.containsExactlyInAnyOrder(listOf( ElementKey(NODE, 1), ElementKey(NODE, 2), ElementKey(NODE, 3) )) - })).thenReturn(listOf( + })}.returns(listOf( ElementGeometryEntry(NODE, 1, geoms[0]), ElementGeometryEntry(NODE, 2, geoms[1]) )) @@ -166,53 +169,53 @@ class OsmQuestControllerTest { } @Test fun countAll() { - on(hiddenDB.countAll()).thenReturn(123L) + every { hiddenDB.countAll() }.returns(123L) assertEquals(123L, ctrl.countAll()) } @Test fun hide() { val quest = osmQuest(questType = ApplicableQuestType) - on(hiddenDB.getTimestamp(eq(quest.key))).thenReturn(555) - on(mapDataSource.getGeometry(quest.elementType, quest.elementId)).thenReturn(pGeom()) + every { hiddenDB.getTimestamp(eq(quest.key)) }.returns(555) + every { mapDataSource.getGeometry(quest.elementType, quest.elementId) }.returns(pGeom()) ctrl.hide(quest.key) - verify(hiddenDB).add(quest.key) - verify(hideListener).onHid(eq(OsmQuestHidden( + verifyInvokedExactlyOnce { hiddenDB.add(quest.key) } + verifyInvokedExactlyOnce { hideListener.onHid(eq(OsmQuestHidden( quest.elementType, quest.elementId, quest.type, quest.position, 555 - ))) - verify(listener).onUpdated( + )))} + verifyInvokedExactlyOnce { listener.onUpdated( addedQuests = eq(emptyList()), deletedQuestKeys = eq(listOf(quest.key)) - ) + )} } @Test fun unhide() { val quest = osmQuest(questType = ApplicableQuestType) - on(hiddenDB.delete(quest.key)).thenReturn(true) - on(hiddenDB.getTimestamp(eq(quest.key))).thenReturn(555) - on(mapDataSource.getGeometry(quest.elementType, quest.elementId)).thenReturn(pGeom()) - on(db.get(quest.key)).thenReturn(quest) + every { hiddenDB.delete(quest.key) }.returns(true) + every { hiddenDB.getTimestamp(eq(quest.key)) }.returns(555) + every { mapDataSource.getGeometry(quest.elementType, quest.elementId) }.returns(pGeom()) + every { db.get(quest.key) }.returns(quest) assertTrue(ctrl.unhide(quest.key)) - verify(hiddenDB).delete(quest.key) - verify(hideListener).onUnhid(eq(OsmQuestHidden( + verifyInvokedExactlyOnce { hiddenDB.delete(quest.key) } + verifyInvokedExactlyOnce { hideListener.onUnhid(eq(OsmQuestHidden( quest.elementType, quest.elementId, quest.type, quest.position, 555 - ))) - verify(listener).onUpdated( + )))} + verifyInvokedExactlyOnce { listener.onUpdated( addedQuests = eq(listOf(quest)), deletedQuestKeys = eq(emptyList()) - ) + )} } @Test fun unhideAll() { - on(hiddenDB.deleteAll()).thenReturn(2) + every { hiddenDB.deleteAll() }.returns(2) assertEquals(2, ctrl.unhideAll()) - verify(listener).onInvalidated() - verify(hideListener).onUnhidAll() + verifyInvokedExactlyOnce { listener.onInvalidated() } + verifyInvokedExactlyOnce { hideListener.onUnhidAll() } } @Test fun `updates quests on notes listener update`() { @@ -220,7 +223,7 @@ class OsmQuestControllerTest { notesListener.onUpdated(added = notes, updated = emptyList(), deleted = emptyList()) - verify(listener).onInvalidated() + verifyInvokedExactlyOnce { listener.onInvalidated() } } @Test fun `updates quests on map data listener update for deleted elements`() { @@ -231,7 +234,7 @@ class OsmQuestControllerTest { ) val keys = listOf(ElementKey(NODE, 1), ElementKey(NODE, 2)) - on(db.getAllForElements(eq(keys))).thenReturn(quests) + every { db.getAllForElements(eq(keys)) }.returns(quests) val deleted = listOf( ElementKey(NODE, 1), @@ -242,18 +245,18 @@ class OsmQuestControllerTest { val expectedDeletedQuestKeys = quests.map { it.key } - verify(db).deleteAll(argThat { it.containsExactlyInAnyOrder(expectedDeletedQuestKeys) }) - verify(db).putAll(argThat { it.isEmpty() }) - verify(listener).onUpdated( + verifyInvokedExactlyOnce { db.deleteAll(matches { it.containsExactlyInAnyOrder(expectedDeletedQuestKeys) }) } + verifyInvokedExactlyOnce { db.putAll(matches { it.isEmpty() }) } + verifyInvokedExactlyOnce { listener.onUpdated( addedQuests = eq(emptyList()), - deletedQuestKeys = argThat { it.containsExactlyInAnyOrder(expectedDeletedQuestKeys) } - ) + deletedQuestKeys = matches { it.containsExactlyInAnyOrder(expectedDeletedQuestKeys) } + )} } @Test fun `calls onInvalidated when cleared quests`() { mapDataListener.onCleared() - verify(db).clear() - verify(listener).onInvalidated() + verifyInvokedExactlyOnce { db.clear() } + verifyInvokedExactlyOnce { listener.onInvalidated() } } @Test fun `updates quests on map data listener update for updated elements`() { @@ -275,8 +278,8 @@ class OsmQuestControllerTest { val previousQuests = listOf(existingApplicableQuest, existingNonApplicableQuest) - on(db.getAllForElements(eq(elements.map { ElementKey(it.type, it.id) }))).thenReturn(previousQuests) - on(mapDataSource.getMapDataWithGeometry(any())).thenReturn(mapData) + every { db.getAllForElements(eq(elements.map { ElementKey(it.type, it.id) })) }.returns(previousQuests) + every { mapDataSource.getMapDataWithGeometry(any()) }.returns(mapData) mapDataListener.onUpdated(mapData, emptyList()) @@ -290,12 +293,12 @@ class OsmQuestControllerTest { val expectedDeletedQuestKeys = listOf(existingNonApplicableQuest.key) - verify(db).deleteAll(eq(expectedDeletedQuestKeys)) - verify(db).putAll(eq(expectedCreatedQuests)) - verify(listener).onUpdated( + verifyInvokedExactlyOnce { db.deleteAll(eq(expectedDeletedQuestKeys)) } + verifyInvokedExactlyOnce { db.putAll(eq(expectedCreatedQuests)) } + verifyInvokedExactlyOnce { listener.onUpdated( addedQuests = eq(expectedCreatedQuests), deletedQuestKeys = eq(expectedDeletedQuestKeys) - ) + )} } @Test fun `updates quests on map data listener replace for bbox`() { @@ -326,11 +329,11 @@ class OsmQuestControllerTest { val previousQuests = listOf(existingApplicableQuest, existingNonApplicableQuest) - on(db.getAllInBBox(bbox)).thenReturn(previousQuests) + every { db.getAllInBBox(bbox) }.returns(previousQuests) - on(notesSource.getAllPositions(any())).thenReturn(listOf(notePos)) + every { notesSource.getAllPositions(any()) }.returns(listOf(notePos)) - on(hiddenDB.getAllIds()).thenReturn(listOf( + every { hiddenDB.getAllIds() }.returns(listOf( OsmQuestKey(NODE, 3L, "ApplicableQuestType2") )) @@ -350,12 +353,12 @@ class OsmQuestControllerTest { val expectedDeletedQuestKeys = listOf(existingNonApplicableQuest.key) - verify(db).deleteAll(eq(expectedDeletedQuestKeys)) - verify(db).putAll(argThat { it.containsExactlyInAnyOrder(expectedCreatedQuests) }) - verify(listener).onUpdated( - addedQuests = argThat { it.containsExactlyInAnyOrder(expectedAddedQuests) }, + verifyInvokedExactlyOnce { db.deleteAll(eq(expectedDeletedQuestKeys)) } + verifyInvokedExactlyOnce { db.putAll(matches { it.containsExactlyInAnyOrder(expectedCreatedQuests) }) } + verifyInvokedExactlyOnce { listener.onUpdated( + addedQuests = matches { it.containsExactlyInAnyOrder(expectedAddedQuests) }, deletedQuestKeys = eq(expectedDeletedQuestKeys) - ) + )} } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloaderTest.kt index 6e142befaf..57263573d3 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloaderTest.kt @@ -2,8 +2,6 @@ package de.westnordost.streetcomplete.data.osmnotes import de.westnordost.osmapi.user.UserApi import de.westnordost.osmapi.user.UserInfo -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respondError @@ -11,6 +9,10 @@ import io.ktor.client.engine.mock.respondOk import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.utils.io.errors.IOException +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlinx.coroutines.runBlocking import java.nio.file.Files import kotlin.test.BeforeTest @@ -24,13 +26,13 @@ class AvatarsDownloaderTest { else -> respondOk("Image Content") } } private val tempFolder = Files.createTempDirectory("images").toFile() - private val userApi: UserApi = mock() + @Mock private val userApi: UserApi = mock(classOf()) private val downloader = AvatarsDownloader(HttpClient(mockEngine), userApi, tempFolder) private val userInfo = UserInfo(100, "Map Enthusiast 530") @BeforeTest fun setUp() { userInfo.profileImageUrl = "http://example.com/BigImage.png" - on(userApi.get(userInfo.id)).thenReturn(userInfo) + every { userApi.get(userInfo.id) }.returns(userInfo) } @Test diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NoteControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NoteControllerTest.kt index 3e22dd7214..bdf1e4aa94 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NoteControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NoteControllerTest.kt @@ -1,149 +1,150 @@ package de.westnordost.streetcomplete.data.osmnotes import de.westnordost.streetcomplete.testutils.bbox -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.note -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import java.lang.Thread.sleep import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class NoteControllerTest { - private lateinit var dao: NoteDao + @Mock private lateinit var dao: NoteDao + @Mock private lateinit var listener: NoteController.Listener private lateinit var noteController: NoteController @BeforeTest fun setUp() { - dao = mock() + dao = mock(classOf()) noteController = NoteController(dao) } @Test fun get() { val note = note(5) - on(dao.get(5L)).thenReturn(note) + every { dao.get(5L) }.returns(note) assertEquals(note, noteController.get(5L)) } @Test fun `getAll note ids`() { val ids = listOf(1L, 2L, 3L) val ret = listOf(note(1), note(2), note(3)) - on(dao.getAll(ids)).thenReturn(ret) + every { dao.getAll(ids) }.returns(ret) assertEquals(ret, noteController.getAll(ids)) } @Test fun `getAll in bbox`() { val bbox = bbox() val ret = listOf(note(1), note(2), note(3)) - on(dao.getAll(bbox)).thenReturn(ret) + every { dao.getAll(bbox) }.returns(ret) assertEquals(ret, noteController.getAll(bbox)) } @Test fun `getAllPositions in bbox`() { val bbox = bbox() val ret = listOf(p(), p(), p()) - on(dao.getAllPositions(bbox)).thenReturn(ret) + every { dao.getAllPositions(bbox) }.returns(ret) assertEquals(ret, noteController.getAllPositions(bbox)) } @Test fun put() { val note = note(1) - - val listener = mock() + every { dao.get(1L) }.returns(null) + val listener = mock(classOf()) noteController.addListener(listener) noteController.put(note) - verify(dao).put(note) + verifyInvokedExactlyOnce { dao.put(note) } sleep(100) - verify(listener).onUpdated(eq(listOf(note)), eq(emptyList()), eq(emptyList())) + verifyInvokedExactlyOnce { listener.onUpdated(listOf(note), emptyList(), emptyList()) } } @Test fun `put existing`() { val note = note(1) - val listener = mock() - on(dao.get(1L)).thenReturn(note) + val listener = mock(classOf()) + every { dao.get(1L) }.returns(note) noteController.addListener(listener) noteController.put(note) - verify(dao).put(note) + verifyInvokedExactlyOnce { dao.put(note) } sleep(100) - verify(listener).onUpdated(eq(emptyList()), eq(listOf(note)), eq(emptyList())) + verifyInvokedExactlyOnce { listener.onUpdated(emptyList(), listOf(note), emptyList()) } } @Test fun delete() { - val listener = mock() - on(dao.delete(1L)).thenReturn(true) + val listener = mock(classOf()) + every { dao.delete(1L) }.returns(true) noteController.addListener(listener) noteController.delete(1L) - verify(dao).delete(1L) + verifyInvokedExactlyOnce { dao.delete(1L) } sleep(100) - verify(listener).onUpdated(eq(emptyList()), eq(emptyList()), eq(listOf(1L))) + verifyInvokedExactlyOnce { listener.onUpdated(emptyList(), emptyList(), listOf(1L)) } } @Test fun `delete non-existing`() { - val listener = mock() - on(dao.delete(1L)).thenReturn(false) + val listener = mock(classOf()) + every { dao.delete(1L) }.returns(false) noteController.addListener(listener) noteController.delete(1L) - verify(dao).delete(1L) - verifyNoInteractions(listener) + verifyInvokedExactlyOnce { dao.delete(1L) } } @Test fun `remove listener`() { - val listener = mock() - + val listener = mock(classOf()) + every { dao.get(0L) }.returns(note(0L)) noteController.addListener(listener) noteController.removeListener(listener) - noteController.put(mock()) - verifyNoInteractions(listener) + noteController.put(note(0)) } @Test fun deleteOlderThan() { val ids = listOf(1L, 2L, 3L) - on(dao.getIdsOlderThan(123L)).thenReturn(ids) - val listener = mock() + every { dao.getIdsOlderThan(123L) }.returns(ids) + every { dao.deleteAll(ids) }.returns(3) + val listener = mock(classOf()) noteController.addListener(listener) assertEquals(3, noteController.deleteOlderThan(123L)) - verify(dao).deleteAll(ids) + verifyInvokedExactlyOnce { dao.deleteAll(ids) } sleep(100) - verify(listener).onUpdated(eq(emptyList()), eq(emptyList()), eq(ids)) + verifyInvokedExactlyOnce { listener.onUpdated(emptyList(), emptyList(), ids) } } @Test fun clear() { - val listener = mock() + val listener = mock(classOf()) noteController.addListener(listener) noteController.clear() - verify(dao).clear() - verify(listener).onCleared() + verifyInvokedExactlyOnce { dao.clear() } + verifyInvokedExactlyOnce { listener.onCleared() } } @Test fun `putAllForBBox when nothing was there before`() { val bbox = bbox() val notes = listOf(note(1), note(2), note(3)) - on(dao.getAll(bbox)).thenReturn(emptyList()) - val listener = mock() + every { dao.getAll(bbox) }.returns(emptyList()) + every { dao.deleteAll(emptySet()) }.returns(0) + val listener = mock(classOf()) noteController.addListener(listener) noteController.putAllForBBox(bbox, notes) - verify(dao).getAll(bbox) - verify(dao).putAll(eq(notes)) - verify(dao).deleteAll(eq(emptySet())) + verifyInvokedExactlyOnce { dao.getAll(bbox) } + verifyInvokedExactlyOnce { dao.putAll(notes) } + verifyInvokedExactlyOnce { dao.deleteAll(emptySet()) } sleep(100) - verify(listener).onUpdated(eq(notes), eq(emptyList()), eq(emptySet())) + verifyInvokedExactlyOnce { listener.onUpdated(notes, emptyList(), emptySet()) } } @Test fun `putAllForBBox when there is something already`() { @@ -154,16 +155,17 @@ class NoteControllerTest { val oldNotes = listOf(note1, note2) // 1 is updated, 2 is deleted, 3 is added val newNotes = listOf(note1, note3) - on(dao.getAll(bbox)).thenReturn(oldNotes) - val listener = mock() + every { dao.getAll(bbox) }.returns(oldNotes) + every { dao.deleteAll(setOf(2L)) }.returns(1) + val listener = mock(classOf()) noteController.addListener(listener) noteController.putAllForBBox(bbox, newNotes) - verify(dao).getAll(bbox) - verify(dao).putAll(eq(newNotes)) - verify(dao).deleteAll(eq(setOf(2L))) + verifyInvokedExactlyOnce { dao.getAll(bbox) } + verifyInvokedExactlyOnce { dao.putAll(newNotes) } + verifyInvokedExactlyOnce { dao.deleteAll(setOf(2L)) } sleep(100) - verify(listener).onUpdated(eq(listOf(note3)), eq(listOf(note1)), eq(setOf(2))) + verifyInvokedExactlyOnce { listener.onUpdated(listOf(note3), listOf(note1), setOf(2)) } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloaderTest.kt index c7f20ef31d..78cdc7f408 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloaderTest.kt @@ -1,34 +1,34 @@ package de.westnordost.streetcomplete.data.osmnotes -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.bbox -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.note -import de.westnordost.streetcomplete.testutils.on +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlinx.coroutines.runBlocking -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.verify import kotlin.test.BeforeTest import kotlin.test.Test class NotesDownloaderTest { - private lateinit var noteController: NoteController - private lateinit var notesApi: NotesApi + @Mock private lateinit var noteController: NoteController + @Mock private lateinit var notesApi: NotesApi @BeforeTest fun setUp() { - noteController = mock() - notesApi = mock() + noteController = mock(classOf()) + notesApi = mock(classOf()) } @Test fun `calls controller with all notes coming from the notes api`() = runBlocking { val note1 = note() val bbox = bbox() - on(notesApi.getAll(any(), anyInt(), anyInt())).thenReturn(listOf(note1)) + every { notesApi.getAll(any(), any(), any()) }.returns(listOf(note1)) val dl = NotesDownloader(notesApi, noteController) dl.download(bbox) - verify(noteController).putAllForBBox(eq(bbox), eq(listOf(note1))) + verifyInvokedExactlyOnce { noteController.putAllForBBox(bbox, listOf(note1)) } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsControllerTest.kt index 0cd4ff8b1c..f13ad55319 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsControllerTest.kt @@ -2,48 +2,49 @@ package de.westnordost.streetcomplete.data.osmnotes.edits import de.westnordost.streetcomplete.data.osm.mapdata.ElementIdUpdate import de.westnordost.streetcomplete.data.osm.mapdata.ElementType -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.note import de.westnordost.streetcomplete.testutils.noteEdit -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.never -import org.mockito.Mockito.verify +import de.westnordost.streetcomplete.testutils.verifyInvokedExactly +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test class NoteEditsControllerTest { private lateinit var ctrl: NoteEditsController - private lateinit var db: NoteEditsDao - private lateinit var listener: NoteEditsSource.Listener + @Mock private lateinit var db: NoteEditsDao + @Mock private lateinit var listener: NoteEditsSource.Listener @BeforeTest fun setUp() { - db = mock() - on(db.delete(anyLong())).thenReturn(true) - on(db.markSynced(anyLong())).thenReturn(true) + db = mock(classOf()) + every { db.delete(any()) }.returns(true) + every { db.markSynced(any()) }.returns(true) - listener = mock() + listener = mock(classOf()) ctrl = NoteEditsController(db) ctrl.addListener(listener) } @Test fun add() { + every { db.add(any()) }.returns(true) ctrl.add(1L, NoteEditAction.COMMENT, p(1.0, 1.0)) - verify(db).add(any()) - verify(listener).onAddedEdit(any()) + verifyInvokedExactlyOnce { db.add(any()) } + verifyInvokedExactlyOnce { listener.onAddedEdit(any()) } } @Test fun syncFailed() { val edit = noteEdit(noteId = 1) ctrl.markSyncFailed(edit) - verify(db).delete(1) - verify(listener).onDeletedEdits(eq(listOf(edit))) + verifyInvokedExactlyOnce { db.delete(1) } + verifyInvokedExactlyOnce { listener.onDeletedEdits(listOf(edit)) } } @Test fun synced() { @@ -53,21 +54,21 @@ class NoteEditsControllerTest { ctrl.markSynced(edit, note) val editSynced = edit.copy(isSynced = true) - verify(db).markSynced(3) - verify(db, never()).updateNoteId(anyLong(), anyLong()) - verify(listener).onSyncedEdit(editSynced) + verifyInvokedExactlyOnce { db.markSynced(3) } + verifyInvokedExactlyOnce { listener.onSyncedEdit(editSynced) } } @Test fun `synced with new id`() { val edit = noteEdit(id = 3, noteId = -100) val note = note(123) + every { db.updateNoteId(-100, 123) }.returns(1) ctrl.markSynced(edit, note) val editSynced = edit.copy(isSynced = true) - verify(db).markSynced(3) - verify(db).updateNoteId(-100L, 123L) - verify(listener).onSyncedEdit(editSynced) + verifyInvokedExactlyOnce { db.markSynced(3) } + verifyInvokedExactlyOnce { db.updateNoteId(-100L, 123L) } + verifyInvokedExactlyOnce { listener.onSyncedEdit(editSynced) } } @Test fun `update element ids`() { @@ -77,8 +78,8 @@ class NoteEditsControllerTest { ElementIdUpdate(ElementType.RELATION, 8, 234), )) - verify(db).replaceTextInUnsynced("osm.org/node/-9 ", "osm.org/node/1234 ") - verify(db).replaceTextInUnsynced("osm.org/way/4 ", "osm.org/way/999 ") - verify(db).replaceTextInUnsynced("osm.org/relation/8 ", "osm.org/relation/234 ") + verifyInvokedExactlyOnce { db.replaceTextInUnsynced("osm.org/node/-9 ", "osm.org/node/1234 ") } + verifyInvokedExactlyOnce { db.replaceTextInUnsynced("osm.org/way/4 ", "osm.org/way/999 ") } + verifyInvokedExactlyOnce { db.replaceTextInUnsynced("osm.org/relation/8 ", "osm.org/relation/234 ") } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploaderTest.kt index 6f0bb85ddc..5331135d9e 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploaderTest.kt @@ -9,49 +9,57 @@ import de.westnordost.streetcomplete.data.osmtracks.TracksApi import de.westnordost.streetcomplete.data.upload.ConflictException import de.westnordost.streetcomplete.data.upload.OnUploadedChangeListener import de.westnordost.streetcomplete.data.user.UserDataSource -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.testutils.coVerifyInvokedExactlyOnce import de.westnordost.streetcomplete.testutils.note import de.westnordost.streetcomplete.testutils.noteEdit -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p +import de.westnordost.streetcomplete.testutils.verifyInvokedExactly +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respondOk +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.coEvery +import io.mockative.every +import io.mockative.mock import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions import kotlin.test.BeforeTest import kotlin.test.Test class NoteEditsUploaderTest { - private lateinit var noteController: NoteController - private lateinit var noteEditsController: NoteEditsController - private lateinit var notesApi: NotesApi - private lateinit var tracksApi: TracksApi + @Mock private lateinit var noteController: NoteController + @Mock private lateinit var noteEditsController: NoteEditsController + @Mock private lateinit var notesApi: NotesApi + @Mock private lateinit var tracksApi: TracksApi + // todo mocking HttpClient doesn't work, no mock class is generated because it's final + // private val validResponseMockEngine = MockEngine { _ -> respondOk("simple response") } private lateinit var imageUploader: StreetCompleteImageUploader - private lateinit var userDataSource: UserDataSource + @Mock private lateinit var userDataSource: UserDataSource private lateinit var uploader: NoteEditsUploader - private lateinit var listener: OnUploadedChangeListener + @Mock private lateinit var listener: OnUploadedChangeListener @BeforeTest fun setUp() { - notesApi = mock() - noteController = mock() - noteEditsController = mock() - userDataSource = mock() + notesApi = mock(classOf()) + noteController = mock(classOf()) + noteEditsController = mock(classOf()) + userDataSource = mock(classOf()) - on(noteEditsController.getOldestNeedingImagesActivation()).thenReturn(null) - on(noteEditsController.getOldestUnsynced()).thenReturn(null) + every { noteEditsController.getOldestNeedingImagesActivation() }.returns(null) + every { noteEditsController.getOldestUnsynced() }.returns(null) - on(notesApi.comment(anyLong(), any())).thenReturn(note()) - on(notesApi.create(any(), any())).thenReturn(note()) + every { notesApi.comment(any(), any()) }.returns(note()) + every { notesApi.create(any(), any()) }.returns(note()) - tracksApi = mock() - imageUploader = mock() - listener = mock() + tracksApi = mock(classOf()) + // imageUploader = StreetCompleteImageUploader(HttpClient(validResponseMockEngine), "osm.org") + imageUploader = mock(classOf()) + listener = mock(classOf()) uploader = NoteEditsUploader(noteEditsController, noteController, userDataSource, notesApi, tracksApi, imageUploader) uploader.uploadedChangeListener = listener @@ -60,7 +68,7 @@ class NoteEditsUploaderTest { @Test fun `cancel upload works`() = runBlocking { val job = launch { uploader.upload() } job.cancelAndJoin() - verifyNoInteractions(noteEditsController, noteController, notesApi, imageUploader) + // verifyNoInteractions(noteEditsController, noteController, notesApi, imageUploader) } @Test fun `upload note comment`() { @@ -68,16 +76,16 @@ class NoteEditsUploaderTest { val edit = noteEdit(noteId = 1L, action = NoteEditAction.COMMENT, text = "abc", pos = pos) val note = note(id = 1L) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(notesApi.comment(anyLong(), any())).thenReturn(note) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { notesApi.comment(any(), any()) }.returns(note) upload() - verify(notesApi).comment(1L, "abc") - verify(noteController).put(note) - verify(noteEditsController).markSynced(edit, note) - verifyNoInteractions(imageUploader) - verify(listener)!!.onUploaded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.comment(1L, "abc") } + verifyInvokedExactlyOnce { noteController.put(note) } + verifyInvokedExactlyOnce { noteEditsController.markSynced(edit, note) } + // verifyNoInteractions(imageUploader) + verifyInvokedExactlyOnce { listener.onUploaded("NOTE", pos) } } @Test fun `upload create note`() { @@ -85,16 +93,16 @@ class NoteEditsUploaderTest { val edit = noteEdit(noteId = -5L, action = NoteEditAction.CREATE, text = "abc", pos = pos) val note = note(123) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(notesApi.create(any(), any())).thenReturn(note) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { notesApi.create(any(), any()) }.returns(note) upload() - verify(notesApi).create(pos, "abc") - verify(noteController).put(note) - verify(noteEditsController).markSynced(edit, note) - verifyNoInteractions(imageUploader) - verify(listener)!!.onUploaded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.create(pos, "abc") } + verifyInvokedExactlyOnce { noteController.put(note) } + verifyInvokedExactlyOnce { noteEditsController.markSynced(edit, note) } + // verifyNoInteractions(imageUploader) + verifyInvokedExactlyOnce { listener.onUploaded("NOTE", pos) } } @Test fun `fail uploading note comment because of a conflict`() { @@ -102,17 +110,17 @@ class NoteEditsUploaderTest { val edit = noteEdit(noteId = 1L, action = NoteEditAction.COMMENT, text = "abc", pos = pos) val note = note(1) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(notesApi.comment(anyLong(), any())).thenThrow(ConflictException()) - on(notesApi.get(1L)).thenReturn(note) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { notesApi.comment(any(), any()) }.throws(ConflictException()) + every { notesApi.get(1L) }.returns(note) upload() - verify(notesApi).comment(1L, "abc") - verify(noteController).put(note) - verify(noteEditsController).markSyncFailed(edit) - verifyNoInteractions(imageUploader) - verify(listener)!!.onDiscarded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.comment(1L, "abc") } + verifyInvokedExactlyOnce { noteController.put(note) } + verifyInvokedExactlyOnce { noteEditsController.markSyncFailed(edit) } + // verifyNoInteractions(imageUploader) + verifyInvokedExactlyOnce { listener.onDiscarded("NOTE", pos) } } @Test fun `fail uploading note comment because note was deleted`() { @@ -120,29 +128,29 @@ class NoteEditsUploaderTest { val edit = noteEdit(noteId = 1L, action = NoteEditAction.COMMENT, text = "abc", pos = pos) val note = note(1) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(notesApi.comment(anyLong(), any())).thenThrow(ConflictException()) - on(notesApi.get(1L)).thenReturn(null) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { notesApi.comment(any(), any()) }.throws(ConflictException()) + every { notesApi.get(1L) }.returns(null) upload() - verify(notesApi).comment(1L, "abc") - verify(noteController).delete(note.id) - verify(noteEditsController).markSyncFailed(edit) - verifyNoInteractions(imageUploader) - verify(listener)!!.onDiscarded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.comment(1L, "abc") } + verifyInvokedExactlyOnce { noteController.delete(note.id) } + verifyInvokedExactlyOnce { noteEditsController.markSyncFailed(edit) } + // verifyNoInteractions(imageUploader) + verifyInvokedExactlyOnce { listener.onDiscarded("NOTE", pos) } } @Test fun `upload several note edits`() { - on(noteEditsController.getOldestUnsynced()).thenReturn(noteEdit()).thenReturn(noteEdit()).thenReturn(null) - on(notesApi.comment(anyLong(), any())).thenReturn(note()) + every { noteEditsController.getOldestUnsynced() }.returnsMany(noteEdit(), noteEdit(), null) + every { notesApi.comment(any(), any()) }.returns(note()) upload() - verify(notesApi, times(2)).comment(anyLong(), any()) - verify(noteController, times(2)).put(any()) - verify(noteEditsController, times(2)).markSynced(any(), any()) - verify(listener, times(2))!!.onUploaded(any(), any()) + verifyInvokedExactly(2) { notesApi.comment(any(), any()) } + verifyInvokedExactly(2) { noteController.put(any()) } + verifyInvokedExactly(2) { noteEditsController.markSynced(any(), any()) } + verifyInvokedExactly(2) { listener.onUploaded(any(), any()) } } @Test fun `upload note comment with attached images`() = runBlocking { @@ -156,19 +164,19 @@ class NoteEditsUploaderTest { ) val note = note(1) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(notesApi.comment(anyLong(), any())).thenReturn(note) - on(imageUploader.upload(any())).thenReturn(listOf("x", "y", "z")) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { notesApi.comment(any(), any()) }.returns(note) + coEvery { imageUploader.upload(any()) }.returns(listOf("x", "y", "z")) upload() - verify(notesApi).comment(1L, "test\n\nAttached photo(s):\nx\ny\nz") - verify(noteController).put(note) - verify(noteEditsController).markSynced(edit, note) - verify(noteEditsController).markImagesActivated(1L) - verify(imageUploader).upload(listOf("a", "b", "c")) - verify(imageUploader).activate(1L) - verify(listener)!!.onUploaded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.comment(1L, "test\n\nAttached photo(s):\nx\ny\nz") } + verifyInvokedExactlyOnce { noteController.put(note) } + verifyInvokedExactlyOnce { noteEditsController.markSynced(edit, note) } + verifyInvokedExactlyOnce { noteEditsController.markImagesActivated(1L) } + coVerifyInvokedExactlyOnce { imageUploader.upload(listOf("a", "b", "c")) } + coVerifyInvokedExactlyOnce { imageUploader.activate(1L) } + verifyInvokedExactlyOnce { listener.onUploaded("NOTE", pos) } } @Test fun `upload create note with attached images`() = runBlocking { @@ -182,19 +190,19 @@ class NoteEditsUploaderTest { ) val note = note(1) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(notesApi.create(any(), any())).thenReturn(note) - on(imageUploader.upload(any())).thenReturn(listOf("x", "y", "z")) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { notesApi.create(any(), any()) }.returns(note) + coEvery { imageUploader.upload(any()) }.returns(listOf("x", "y", "z")) upload() - verify(notesApi).create(pos, "test\n\nAttached photo(s):\nx\ny\nz") - verify(noteController).put(note) - verify(noteEditsController).markSynced(edit, note) - verify(noteEditsController).markImagesActivated(1L) - verify(imageUploader).upload(listOf("a", "b", "c")) - verify(imageUploader).activate(1L) - verify(listener)!!.onUploaded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.create(pos, "test\n\nAttached photo(s):\nx\ny\nz") } + verifyInvokedExactlyOnce { noteController.put(note) } + verifyInvokedExactlyOnce { noteEditsController.markSynced(edit, note) } + verifyInvokedExactlyOnce { noteEditsController.markImagesActivated(1L) } + coVerifyInvokedExactlyOnce { imageUploader.upload(listOf("a", "b", "c")) } + coVerifyInvokedExactlyOnce { imageUploader.activate(1L) } + verifyInvokedExactlyOnce { listener.onUploaded("NOTE", pos) } } @Test fun `upload create note with attached GPS trace`() = runBlocking { @@ -208,28 +216,28 @@ class NoteEditsUploaderTest { ) val note = note(1) - on(noteEditsController.getOldestUnsynced()).thenReturn(edit).thenReturn(null) - on(userDataSource.userName).thenReturn("blah mc/Blah") - on(notesApi.create(any(), any())).thenReturn(note) - on(tracksApi.create(edit.track, edit.text)).thenReturn(988) + every { noteEditsController.getOldestUnsynced() }.returnsMany(edit, null) + every { userDataSource.userName }.returns("blah mc/Blah") + every { notesApi.create(any(), any()) }.returns(note) + every { tracksApi.create(edit.track, edit.text) }.returns(988) upload() - verify(notesApi).create(pos, "test\n\nGPS Trace: https://www.openstreetmap.org/user/blah%20mc%2FBlah/traces/988\n") - verify(noteController).put(note) - verify(noteEditsController).markSynced(edit, note) - verify(listener)!!.onUploaded("NOTE", pos) + verifyInvokedExactlyOnce { notesApi.create(pos, "test\n\nGPS Trace: https://www.openstreetmap.org/user/blah%20mc%2FBlah/traces/988\n") } + verifyInvokedExactlyOnce { noteController.put(note) } + verifyInvokedExactlyOnce { noteEditsController.markSynced(edit, note) } + verifyInvokedExactlyOnce { listener.onUploaded("NOTE", pos) } } @Test fun `upload missed image activations`(): Unit = runBlocking { val edit = noteEdit(noteId = 3) - on(noteEditsController.getOldestNeedingImagesActivation()).thenReturn(edit).thenReturn(null) + every { noteEditsController.getOldestNeedingImagesActivation() }.returnsMany(edit, null) upload() - verify(imageUploader).activate(3) - verify(noteEditsController).markImagesActivated(1L) + coVerifyInvokedExactlyOnce { imageUploader.activate(3) } + verifyInvokedExactlyOnce { noteEditsController.markImagesActivated(1L) } } private fun upload() = runBlocking { diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSourceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSourceTest.kt index defeafd9ee..6f56df356c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSourceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/edits/NotesWithEditsSourceTest.kt @@ -6,16 +6,18 @@ import de.westnordost.streetcomplete.data.osmnotes.NoteComment import de.westnordost.streetcomplete.data.osmnotes.NoteController import de.westnordost.streetcomplete.data.user.User import de.westnordost.streetcomplete.data.user.UserDataSource -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.bbox import de.westnordost.streetcomplete.testutils.comment -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.note import de.westnordost.streetcomplete.testutils.noteEdit -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.mock +import io.mockative.classOf +import io.mockative.every import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -25,24 +27,27 @@ import kotlin.test.assertTrue class NotesWithEditsSourceTest { private lateinit var src: NotesWithEditsSource - private lateinit var noteController: NoteController - private lateinit var noteListener: NoteController.Listener - private lateinit var noteEditsController: NoteEditsController - private lateinit var noteEditsListener: NoteEditsSource.Listener - private lateinit var userDataSource: UserDataSource + @Mock private lateinit var noteController: NoteController + @Mock private lateinit var noteListener: NoteController.Listener + @Mock private lateinit var noteEditsController: NoteEditsController + @Mock private lateinit var noteEditsListener: NoteEditsSource.Listener + @Mock private lateinit var userDataSource: UserDataSource + + // dummy + @Mock private val dummy = mock(classOf()) @BeforeTest fun setUp() { - noteController = mock() - noteEditsController = mock() - userDataSource = mock() + noteController = mock(classOf()) + noteEditsController = mock(classOf()) + userDataSource = mock(classOf()) - on(noteController.addListener(any())).then { invocation -> - noteListener = invocation.getArgument(0) + every { noteController.addListener(any()) }.invokes { args -> + noteListener = args[0] as NoteController.Listener Unit } - on(noteEditsController.addListener(any())).then { invocation -> - noteEditsListener = invocation.getArgument(0) + every { noteEditsController.addListener(any()) }.invokes { args -> + noteEditsListener = args[0] as NoteEditsSource.Listener Unit } @@ -53,8 +58,8 @@ class NotesWithEditsSourceTest { @Test fun `get returns nothing`() { - on(noteController.get(1)).thenReturn(null) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(emptyList()) + every { noteController.get(1) }.returns(null) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(emptyList()) assertNull(src.get(1)) } @@ -62,8 +67,8 @@ class NotesWithEditsSourceTest { @Test fun `get returns original note`() { val note = note(1) - on(noteController.get(1)).thenReturn(note) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(emptyList()) + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(emptyList()) assertEquals(note, src.get(1)) } @@ -78,9 +83,10 @@ class NotesWithEditsSourceTest { noteEdit(noteId = 1, action = NoteEditAction.COMMENT, text = "test2", timestamp = 500) ) - on(userDataSource.userId).thenReturn(-1) - on(noteController.get(1)).thenReturn(note) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(edits) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(edits) assertEquals(listOf(comment, addedComment), src.get(1)!!.comments) } @@ -104,9 +110,10 @@ class NotesWithEditsSourceTest { timestamp = 500 )) - on(userDataSource.userId).thenReturn(-1) - on(noteController.get(1)).thenReturn(note) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(edits) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(edits) assertEquals(listOf(comment, addedComment), src.get(1)!!.comments) } @@ -121,10 +128,10 @@ class NotesWithEditsSourceTest { noteEdit(noteId = 1, action = NoteEditAction.COMMENT, text = "test2", timestamp = 500) ) - on(userDataSource.userId).thenReturn(23) - on(userDataSource.userName).thenReturn("test user") - on(noteController.get(1)).thenReturn(note) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(edits) + every { userDataSource.userId }.returns(23) + every { userDataSource.userName }.returns("test user") + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(edits) assertEquals(listOf(comment1, comment2), src.get(1)!!.comments) } @@ -141,9 +148,10 @@ class NotesWithEditsSourceTest { noteEdit(noteId = 1, action = NoteEditAction.COMMENT, text = "test3", timestamp = 800), ) - on(userDataSource.userId).thenReturn(-1) - on(noteController.get(1)).thenReturn(note) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(edits) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(edits) assertEquals(listOf(comment1, comment2, comment3), src.get(1)!!.comments) } @@ -164,9 +172,10 @@ class NotesWithEditsSourceTest { noteEdit(noteId = -12, pos = p, action = NoteEditAction.CREATE, text = "test12", timestamp = 123) ) - on(userDataSource.userId).thenReturn(-1) - on(noteController.get(1)).thenReturn(null) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(edits) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(null) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(edits) assertEquals(expectedNote, src.get(1)!!) } @@ -189,9 +198,10 @@ class NotesWithEditsSourceTest { noteEdit(noteId = -12, pos = p, action = NoteEditAction.COMMENT, text = "test34", timestamp = 234), ) - on(userDataSource.userId).thenReturn(-1) - on(noteController.get(1)).thenReturn(null) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(edits) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(null) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(edits) assertEquals(expectedNote, src.get(1)!!) } @@ -205,8 +215,8 @@ class NotesWithEditsSourceTest { val ps1 = listOf(p(3.0, 2.0), p(1.0, 3.0)) val ps2 = listOf(p(3.0, 2.0), p(5.0, 1.0)) - on(noteController.getAllPositions(any())).thenReturn(ps1) - on(noteEditsController.getAllUnsyncedPositions(any())).thenReturn(ps2) + every { noteController.getAllPositions(any()) }.returns(ps1) + every { noteEditsController.getAllUnsyncedPositions(any()) }.returns(ps2) val positions = src.getAllPositions(bbox) @@ -219,8 +229,8 @@ class NotesWithEditsSourceTest { @Test fun `getAll returns nothing`() { - on(noteController.getAll(any())).thenReturn(emptyList()) - on(noteEditsController.getAllUnsynced(any())).thenReturn(emptyList()) + every { noteController.getAll(any()) }.returns(emptyList()) + every { noteEditsController.getAllUnsynced(any()) }.returns(emptyList()) assertTrue(src.getAll(bbox).isEmpty()) } @@ -228,17 +238,18 @@ class NotesWithEditsSourceTest { @Test fun `getAll returns original notes`() { val notes = listOf(note(1), note(2)) - on(noteController.getAll(any())).thenReturn(notes) - on(noteEditsController.getAllUnsynced(any())).thenReturn(emptyList()) + every { noteController.getAll(any()) }.returns(notes) + every { noteEditsController.getAllUnsynced(any()) }.returns(emptyList()) assertTrue(src.getAll(bbox).containsExactlyInAnyOrder(notes)) } @Test fun `getAll returns updated notes`() { - on(userDataSource.userId).thenReturn(-1) - on(noteController.getAll(any())).thenReturn(initialNotes1) - on(noteEditsController.getAllUnsynced(any())).thenReturn(edits1) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.getAll(any()) }.returns(initialNotes1) + every { noteEditsController.getAllUnsynced(any()) }.returns(edits1) assertEquals(expectedNotes1.toSet(), src.getAll(bbox).toSet()) } @@ -248,13 +259,16 @@ class NotesWithEditsSourceTest { //region NoteEditsSource.Listener @Test fun `onDeletedEdits relays updated note`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) val note = note(1) val edit = noteEdit(noteId = 1) - on(noteController.get(1)).thenReturn(note) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(listOf(edit)) noteEditsListener.onDeletedEdits(listOf(edit)) @@ -262,12 +276,13 @@ class NotesWithEditsSourceTest { } @Test fun `onDeletedEdits relays deleted note`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) val edit = noteEdit(noteId = 1) - on(noteController.get(1)).thenReturn(null) + every { noteController.get(1) }.returns(null) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(listOf(edit)) noteEditsListener.onDeletedEdits(listOf(edit)) @@ -275,13 +290,16 @@ class NotesWithEditsSourceTest { } @Test fun `onAddedEdit relays updated note`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) val note = note(id = 1) val edit = noteEdit(noteId = 1) - on(noteController.get(1)).thenReturn(note) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(note) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(listOf(edit)) noteEditsListener.onAddedEdit(edit) @@ -289,7 +307,7 @@ class NotesWithEditsSourceTest { } @Test fun `onAddedEdit relays added note`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) val p = p(1.0, 1.0) @@ -298,8 +316,10 @@ class NotesWithEditsSourceTest { )) val edit = noteEdit(noteId = 1, id = -1, action = NoteEditAction.CREATE, text = "abc", timestamp = 123L, pos = p) - on(noteController.get(1)).thenReturn(null) - on(noteEditsController.getAllUnsyncedForNote(1)).thenReturn(listOf(edit)) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteController.get(1) }.returns(null) + every { noteEditsController.getAllUnsyncedForNote(1) }.returns(listOf(edit)) noteEditsListener.onAddedEdit(edit) @@ -311,14 +331,14 @@ class NotesWithEditsSourceTest { //region NoteController.Listener @Test fun `onUpdated passes through notes because there are no edits`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) val added = listOf(note(1), note(2)) val updated = listOf(note(3), note(4)) val deleted = listOf(1L, 2L) - on(noteEditsController.getAllUnsynced()).thenReturn(emptyList()) + every { noteEditsController.getAllUnsynced() }.returns(emptyList()) noteListener.onUpdated(added, updated, deleted) @@ -326,10 +346,12 @@ class NotesWithEditsSourceTest { } @Test fun `onUpdated applies edits on top of passed added notes`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) - on(noteEditsController.getAllUnsynced()).thenReturn(edits1) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteEditsController.getAllUnsynced() }.returns(edits1) noteListener.onUpdated(initialNotes1, emptyList(), emptyList()) @@ -337,10 +359,12 @@ class NotesWithEditsSourceTest { } @Test fun `onUpdated applies edits on top of passed updated notes`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) - on(noteEditsController.getAllUnsynced()).thenReturn(edits1) + every { userDataSource.userId }.returns(-1) + every { userDataSource.userName }.returns("") + every { noteEditsController.getAllUnsynced() }.returns(edits1) noteListener.onUpdated(emptyList(), initialNotes1, emptyList()) @@ -348,11 +372,11 @@ class NotesWithEditsSourceTest { } @Test fun `onCleared passes through call`() { - val listener = mock() + val listener = mock(classOf()) src.addListener(listener) noteListener.onCleared() - verify(listener).onCleared() + verifyInvokedExactlyOnce { listener.onCleared() } } //endregion @@ -393,10 +417,10 @@ private fun checkListenerCalledWith( updated: Collection = emptyList(), deleted: Collection = emptyList() ) { - on(listener).then { invocation -> - val actuallyAdded = invocation.getArgument>(0) - val actuallyUpdated = invocation.getArgument>(1) - val actuallyDeleted = invocation.getArgument>(2) + every { listener.onUpdated(any(), any(), any()) }.invokes { args -> + val actuallyAdded = args[0] as Collection + val actuallyUpdated = args[1] as Collection + val actuallyDeleted = args[0] as Collection assertEquals(added, actuallyAdded) assertEquals(updated, actuallyUpdated) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestControllerTest.kt index 8d6ea1527c..bb0cb7e927 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestControllerTest.kt @@ -6,17 +6,19 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NotesWithEditsSource import de.westnordost.streetcomplete.data.user.User import de.westnordost.streetcomplete.data.user.UserDataSource import de.westnordost.streetcomplete.data.user.UserLoginStatusSource -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.argThat import de.westnordost.streetcomplete.testutils.bbox import de.westnordost.streetcomplete.testutils.comment -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.note -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.matches +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -25,111 +27,137 @@ import kotlin.test.assertNull class OsmNoteQuestControllerTest { + @Mock private lateinit var noteSource: NotesWithEditsSource + @Mock private lateinit var hiddenDB: NoteQuestsHiddenDao + @Mock private lateinit var userDataSource: UserDataSource + @Mock private lateinit var userLoginStatusSource: UserLoginStatusSource + @Mock private lateinit var prefs: ObservableSettings private lateinit var ctrl: OsmNoteQuestController + @Mock private lateinit var listener: OsmNoteQuestSource.Listener + @Mock private lateinit var hideListener: OsmNoteQuestsHiddenSource.Listener private lateinit var noteUpdatesListener: NotesWithEditsSource.Listener private lateinit var userLoginListener: UserLoginStatusSource.Listener - @BeforeTest fun setUp() { - noteSource = mock() - hiddenDB = mock() - userDataSource = mock() - userLoginStatusSource = mock() - prefs = mock() + @BeforeTest + fun setUp() { + noteSource = mock(classOf()) + hiddenDB = mock(classOf()) + userDataSource = mock(classOf()) + userLoginStatusSource = mock(classOf()) + prefs = mock(classOf()) - listener = mock() - hideListener = mock() + listener = mock(classOf()) + hideListener = mock(classOf()) - on(noteSource.addListener(any())).then { invocation -> - noteUpdatesListener = invocation.getArgument(0) + every { noteSource.addListener(any()) }.invokes { arguments -> + noteUpdatesListener = arguments[0] as NotesWithEditsSource.Listener Unit } - on(userLoginStatusSource.addListener(any())).then { invocation -> - userLoginListener = invocation.getArgument(0) + every { userLoginStatusSource.addListener(any()) }.invokes { arguments -> + userLoginListener = arguments[0] as UserLoginStatusSource.Listener Unit } - ctrl = OsmNoteQuestController(noteSource, hiddenDB, userDataSource, userLoginStatusSource, prefs) + ctrl = OsmNoteQuestController( + noteSource, + hiddenDB, + userDataSource, + userLoginStatusSource, + prefs + ) ctrl.addListener(listener) ctrl.addListener(hideListener) } - @Test fun hide() { + @Test + fun hide() { val note = note(1) val ts = 123L - on(hiddenDB.getTimestamp(1)).thenReturn(ts) - on(noteSource.get(1)).thenReturn(note) + every { hiddenDB.getTimestamp(1) }.returns(ts) + every { noteSource.get(1) }.returns(note) ctrl.hide(1) - verify(hiddenDB).add(1) - verify(hideListener).onHid(eq(OsmNoteQuestHidden(note, ts))) - verify(listener).onUpdated( - addedQuests = eq(emptyList()), - deletedQuestIds = eq(listOf(1)) - ) + verifyInvokedExactlyOnce { hiddenDB.add(1) } + verifyInvokedExactlyOnce { hideListener.onHid(eq(OsmNoteQuestHidden(note, ts))) } + verifyInvokedExactlyOnce { + listener.onUpdated( + addedQuests = eq(emptyList()), + deletedQuestIds = eq(listOf(1)) + ) + } } - @Test fun unhide() { + @Test + fun unhide() { val note = note(1) val ts = 123L - on(hiddenDB.getTimestamp(1)).thenReturn(ts) - on(noteSource.get(1)).thenReturn(note) - on(hiddenDB.delete(1)).thenReturn(true) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(true) + every { hiddenDB.getTimestamp(1) }.returns(ts) + every { noteSource.get(1) }.returns(note) + every { hiddenDB.delete(1) }.returns(true) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(true) ctrl.unhide(1) - verify(hideListener).onUnhid(eq(OsmNoteQuestHidden(note, ts))) - verify(listener).onUpdated( - addedQuests = eq(listOf(OsmNoteQuest(1, note.position))), - deletedQuestIds = eq(emptyList()) - ) + verifyInvokedExactlyOnce { hideListener.onUnhid(eq(OsmNoteQuestHidden(note, ts))) } + verifyInvokedExactlyOnce { + listener.onUpdated( + addedQuests = eq(listOf(OsmNoteQuest(1, note.position))), + deletedQuestIds = eq(emptyList()) + ) + } } - @Test fun unhideAll() { + @Test + fun unhideAll() { val hiddenNoteIds = listOf(1, 2, 3) val hiddenNotes = listOf( note(1), note(2), note(3) ) - on(hiddenDB.getAllIds()).thenReturn(hiddenNoteIds) - on(noteSource.getAll(hiddenNoteIds)).thenReturn(hiddenNotes) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(true) + every { hiddenDB.getAllIds() }.returns(hiddenNoteIds) + every { noteSource.getAll(hiddenNoteIds) }.returns(hiddenNotes) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(true) ctrl.unhideAll() val expectedQuests = hiddenNotes.map { OsmNoteQuest(it.id, it.position) } - verify(hiddenDB).deleteAll() - verify(hideListener).onUnhidAll() - verify(listener).onUpdated( - addedQuests = eq(expectedQuests), - deletedQuestIds = eq(emptyList()) - ) + verifyInvokedExactlyOnce { hiddenDB.deleteAll() } + verifyInvokedExactlyOnce { hideListener.onUnhidAll() } + verifyInvokedExactlyOnce { + listener.onUpdated( + addedQuests = eq(expectedQuests), + deletedQuestIds = eq(emptyList()) + ) + } } - @Test fun getAllHiddenNewerThan() { + @Test + fun getAllHiddenNewerThan() { val note1 = note(1) val note2 = note(2) - on(hiddenDB.getNewerThan(123L)).thenReturn(listOf( - NoteIdWithTimestamp(1, 300), - NoteIdWithTimestamp(2, 500), - NoteIdWithTimestamp(3, 600), // missing note - )) - on(noteSource.getAll(eq(listOf(1L, 2L, 3L)))).thenReturn(listOf(note1, note2)) + every { hiddenDB.getNewerThan(123L) }.returns( + listOf( + NoteIdWithTimestamp(1, 300), + NoteIdWithTimestamp(2, 500), + NoteIdWithTimestamp(3, 600), // missing note + ) + ) + every { noteSource.getAll(eq(listOf(1L, 2L, 3L))) }.returns(listOf(note1, note2)) assertEquals( listOf( @@ -140,71 +168,128 @@ class OsmNoteQuestControllerTest { ) } - @Test fun countAll() { - on(hiddenDB.countAll()).thenReturn(123L) + @Test + fun countAll() { + every { hiddenDB.countAll() }.returns(123L) assertEquals(123L, ctrl.countAll()) } - @Test fun `get hidden returns null`() { - on(noteSource.get(1)).thenReturn(note(1)) - on(hiddenDB.contains(1)).thenReturn(true) + @Test + fun `get hidden returns null`() { + every { noteSource.get(1) }.returns(note(1)) + every { hiddenDB.contains(1) }.returns(true) assertNull(ctrl.getVisible(1)) } - @Test fun `get missing returns null`() { - on(noteSource.get(1)).thenReturn(null) + @Test + fun `get missing returns null`() { + every { noteSource.get(1) }.returns(null) assertNull(ctrl.getVisible(1)) } - @Test fun `get note quest with comment from user returns null`() { - on(noteSource.get(1)).thenReturn(note(comments = listOf( - comment(text = "test?", user = User(id = 100, "Blaubär")), - comment(text = "test", user = User(id = 1, "Blubbi")) - ))) - on(userDataSource.userId).thenReturn(1) + @Test + fun `get note quest with comment from user returns null`() { + every { noteSource.get(1) }.returns( + note( + comments = listOf( + comment(text = "test?", user = User(id = 100, "Blaubär")), + comment(text = "test", user = User(id = 1, "Blubbi")) + ) + ) + ) + every { userDataSource.userId }.returns(1) assertNull(ctrl.getVisible(1)) } - @Test fun `get note quest with comment from user that contains a survey required marker returns non-null`() { - on(noteSource.get(1)).thenReturn(note(comments = listOf( - comment(text = "test?", user = User(id = 100, "Blaubär")), - comment(text = "ok but #surveyme", user = User(id = 1, "Blubbi")), - ))) - on(userDataSource.userId).thenReturn(1) + @Test + fun `get note quest with comment from user that contains a survey required marker returns non-null`() { + every { noteSource.get(1) }.returns( + note( + comments = listOf( + comment(text = "test?", user = User(id = 100, "Blaubär")), + comment(text = "ok but #surveyme", user = User(id = 1, "Blubbi")), + ) + ) + ) + every { userDataSource.userId }.returns(1) assertNotNull(ctrl.getVisible(1)) } - @Test fun `get quest not phrased as question returns null`() { - on(noteSource.get(1)).thenReturn(note(comments = listOf( - comment(text = "test") - ))) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(false) + @Test + fun `get quest not phrased as question returns null`() { + every { noteSource.get(1) }.returns( + note( + comments = listOf( + comment(text = "test") + ) + ) + ) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(false) assertNull(ctrl.getVisible(1)) } - @Test fun `get quest phrased as question returns non-null`() { - on(noteSource.get(1)).thenReturn(note( - 1, - position = p(1.0, 1.0), - comments = listOf(comment(text = "test?")) - )) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(false) + @Test + fun `get quest phrased as question returns non-null`() { + every { noteSource.get(1) }.returns( + note( + 1, + position = p(1.0, 1.0), + comments = listOf(comment(text = "test?")) + ) + ) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(false) assertEquals(OsmNoteQuest(1, p(1.0, 1.0)), ctrl.getVisible(1)) } - @Test fun `get quest phrased as question in other scripts returns non-null`() { - on(noteSource.get(1)).thenReturn(note(1, comments = listOf(comment(text = "Greek question mark: ;")))) - on(noteSource.get(2)).thenReturn(note(2, comments = listOf(comment(text = "semicolon: ;")))) - on(noteSource.get(3)).thenReturn(note(3, comments = listOf(comment(text = "mirrored question mark: ؟")))) - on(noteSource.get(4)).thenReturn(note(4, comments = listOf(comment(text = "Armenian question mark: ՞")))) - on(noteSource.get(5)).thenReturn(note(5, comments = listOf(comment(text = "Ethiopian question mark: ፧")))) - on(noteSource.get(6)).thenReturn(note(6, comments = listOf(comment(text = "Vai question mark: ꘏")))) - on(noteSource.get(7)).thenReturn(note(7, comments = listOf(comment(text = "full width question mark: ?")))) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(false) + @Test + fun `get quest phrased as question in other scripts returns non-null`() { + every { noteSource.get(1) }.returns( + note( + 1, + comments = listOf(comment(text = "Greek question mark: ;")) + ) + ) + every { noteSource.get(2) }.returns( + note( + 2, + comments = listOf(comment(text = "semicolon: ;")) + ) + ) + every { noteSource.get(3) }.returns( + note( + 3, + comments = listOf(comment(text = "mirrored question mark: ؟")) + ) + ) + every { noteSource.get(4) }.returns( + note( + 4, + comments = listOf(comment(text = "Armenian question mark: ՞")) + ) + ) + every { noteSource.get(5) }.returns( + note( + 5, + comments = listOf(comment(text = "Ethiopian question mark: ፧")) + ) + ) + every { noteSource.get(6) }.returns( + note( + 6, + comments = listOf(comment(text = "Vai question mark: ꘏")) + ) + ) + every { noteSource.get(7) }.returns( + note( + 7, + comments = listOf(comment(text = "full width question mark: ?")) + ) + ) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(false) assertEquals(1, ctrl.getVisible(1)?.id) assertEquals(2, ctrl.getVisible(2)?.id) @@ -215,37 +300,44 @@ class OsmNoteQuestControllerTest { assertEquals(7, ctrl.getVisible(7)?.id) } - @Test fun `get quest with comment containing survey required marker returns non-null`() { - on(noteSource.get(1)).thenReturn(note( - 1, - position = p(1.0, 1.0), - comments = listOf(comment(text = "test #surveyme")) - )) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(false) + @Test + fun `get quest with comment containing survey required marker returns non-null`() { + every { noteSource.get(1) }.returns( + note( + 1, + position = p(1.0, 1.0), + comments = listOf(comment(text = "test #surveyme")) + ) + ) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(false) assertEquals(OsmNoteQuest(1, p(1.0, 1.0)), ctrl.getVisible(1)) } - @Test fun `get quest not phrased as question returns non-null by preference`() { - on(noteSource.get(1)).thenReturn(note( - 1, - position = p(1.0, 1.0), - comments = listOf(comment(text = "test")) - )) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(true) + @Test + fun `get quest not phrased as question returns non-null by preference`() { + every { noteSource.get(1) }.returns( + note( + 1, + position = p(1.0, 1.0), + comments = listOf(comment(text = "test")) + ) + ) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(true) assertEquals(OsmNoteQuest(1, p(1.0, 1.0)), ctrl.getVisible(1)) } // not doing all the tests for getAll again because it uses the same functions - @Test fun getAll() { + @Test + fun getAll() { val bbox = bbox() val notes = listOf(note(1), note(2), note(3)) - on(hiddenDB.getAllIds()).thenReturn(emptyList()) - on(noteSource.getAll(bbox)).thenReturn(notes) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(true) + every { hiddenDB.getAllIds() }.returns(emptyList()) + every { noteSource.getAll(bbox) }.returns(notes) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(true) val expectedQuests = notes.map { OsmNoteQuest(it.id, it.position) } @@ -255,26 +347,29 @@ class OsmNoteQuestControllerTest { ) } - @Test fun `calls onInvalidated when logged in`() { + @Test + fun `calls onInvalidated when logged in`() { userLoginListener.onLoggedIn() - verify(listener).onInvalidated() + verifyInvokedExactlyOnce { listener.onInvalidated() } } - @Test fun `calls onInvalidated when cleared notes`() { + @Test + fun `calls onInvalidated when cleared notes`() { noteUpdatesListener.onCleared() - verify(listener).onInvalidated() + verifyInvokedExactlyOnce { listener.onInvalidated() } } - @Test fun `calls onUpdated when notes changed`() { + @Test + fun `calls onUpdated when notes changed`() { // note 1 is added // note 2 is not eligible // note 3 is added/updated // note 4 is not eligible -> delete it // note 5 is deleted - on(hiddenDB.getAllIds()).thenReturn(listOf(2, 4)) - on(prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false)).thenReturn(true) + every { hiddenDB.getAllIds() }.returns(listOf(2, 4)) + every { prefs.getBoolean(Prefs.SHOW_NOTES_NOT_PHRASED_AS_QUESTIONS, false) }.returns(true) noteUpdatesListener.onUpdated( added = listOf( @@ -285,14 +380,18 @@ class OsmNoteQuestControllerTest { deleted = listOf(5) ) - verify(listener).onUpdated( - addedQuests = argThat { - it.containsExactlyInAnyOrder(listOf( - OsmNoteQuest(1, p()), - OsmNoteQuest(3, p()), - )) - }, - deletedQuestIds = argThat { it.containsExactlyInAnyOrder(listOf(4, 5)) } - ) + verifyInvokedExactlyOnce { + listener.onUpdated( + addedQuests = matches { + it.containsExactlyInAnyOrder( + listOf( + OsmNoteQuest(1, p()), + OsmNoteQuest(3, p()), + ) + ) + }, + deletedQuestIds = matches { it.containsExactlyInAnyOrder(listOf(4, 5)) } + ) + } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/quest/UnsyncedChangesCountSourceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/quest/UnsyncedChangesCountSourceTest.kt index 2ba4be3d30..bacf6a560e 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/quest/UnsyncedChangesCountSourceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/quest/UnsyncedChangesCountSourceTest.kt @@ -2,71 +2,77 @@ package de.westnordost.streetcomplete.data.quest import de.westnordost.streetcomplete.data.UnsyncedChangesCountSource import de.westnordost.streetcomplete.data.osm.edits.ElementEdit +import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction +import de.westnordost.streetcomplete.data.osm.edits.ElementEditType import de.westnordost.streetcomplete.data.osm.edits.ElementEditsSource import de.westnordost.streetcomplete.data.osm.osmquests.OsmQuestSource import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsSource import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestSource -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.noteEdit -import de.westnordost.streetcomplete.testutils.on +import de.westnordost.streetcomplete.testutils.pGeom +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlinx.coroutines.runBlocking -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions -import org.mockito.invocation.InvocationOnMock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class UnsyncedChangesCountSourceTest { - private lateinit var osmQuestSource: OsmQuestSource - private lateinit var osmNoteQuestSource: OsmNoteQuestSource - private lateinit var noteEditsSource: NoteEditsSource - private lateinit var elementEditsSource: ElementEditsSource + @Mock private lateinit var osmQuestSource: OsmQuestSource + @Mock private lateinit var osmNoteQuestSource: OsmNoteQuestSource + @Mock private lateinit var noteEditsSource: NoteEditsSource + @Mock private lateinit var elementEditsSource: ElementEditsSource + + @Mock private lateinit var elementEditType: ElementEditType + @Mock private lateinit var elementEditAction: ElementEditAction private lateinit var noteQuestListener: OsmNoteQuestSource.Listener private lateinit var questListener: OsmQuestSource.Listener private lateinit var noteEditsListener: NoteEditsSource.Listener private lateinit var elementEditsListener: ElementEditsSource.Listener - private lateinit var listener: UnsyncedChangesCountSource.Listener + @Mock private lateinit var listener: UnsyncedChangesCountSource.Listener private lateinit var source: UnsyncedChangesCountSource private val baseCount = 3 + 4 @BeforeTest fun setUp() { - osmQuestSource = mock() - on(osmQuestSource.addListener(any())).then { invocation: InvocationOnMock -> - questListener = invocation.arguments[0] as OsmQuestSource.Listener + osmQuestSource = mock(classOf()) + every { osmQuestSource.addListener(any()) }.invokes { arguments -> + questListener = arguments[0] as OsmQuestSource.Listener Unit } - osmNoteQuestSource = mock() - on(osmNoteQuestSource.addListener(any())).then { invocation: InvocationOnMock -> - noteQuestListener = invocation.arguments[0] as OsmNoteQuestSource.Listener + osmNoteQuestSource = mock(classOf()) + every { osmNoteQuestSource.addListener(any()) }.invokes { arguments -> + noteQuestListener = arguments[0] as OsmNoteQuestSource.Listener Unit } - noteEditsSource = mock() - on(noteEditsSource.addListener(any())).then { invocation: InvocationOnMock -> - noteEditsListener = invocation.arguments[0] as NoteEditsSource.Listener + noteEditsSource = mock(classOf()) + every { noteEditsSource.addListener(any()) }.invokes { arguments -> + noteEditsListener = arguments[0] as NoteEditsSource.Listener Unit } - elementEditsSource = mock() - on(elementEditsSource.addListener(any())).then { invocation: InvocationOnMock -> - elementEditsListener = invocation.arguments[0] as ElementEditsSource.Listener + elementEditsSource = mock(classOf()) + every { elementEditsSource.addListener(any()) }.invokes { arguments -> + elementEditsListener = arguments[0] as ElementEditsSource.Listener Unit } - on(noteEditsSource.getUnsyncedCount()).thenReturn(3) - on(elementEditsSource.getUnsyncedCount()).thenReturn(4) - on(elementEditsSource.getPositiveUnsyncedCount()).thenReturn(2) + every { noteEditsSource.getUnsyncedCount() }.returns(3) + every { elementEditsSource.getUnsyncedCount() }.returns(4) + every { elementEditsSource.getPositiveUnsyncedCount() }.returns(2) source = UnsyncedChangesCountSource(noteEditsSource, elementEditsSource) - listener = mock() + listener = mock(classOf()) source.addListener(listener) } @@ -75,45 +81,48 @@ class UnsyncedChangesCountSourceTest { } @Test fun `add unsynced element edit triggers listener`() { - val edit = mock() - on(edit.isSynced).thenReturn(false) + elementEditType = mock(classOf()) + elementEditAction = mock(classOf()) + val edit = ElementEdit(1, elementEditType, pGeom(), "a", 1L, false, elementEditAction, true) + // every { edit.isSynced }.returns(false) elementEditsListener.onAddedEdit(edit) - verify(listener).onIncreased() + verifyInvokedExactlyOnce { listener.onIncreased() } } @Test fun `remove unsynced element edit triggers listener`() { - val edit = mock() - on(edit.isSynced).thenReturn(false) + elementEditType = mock(classOf()) + elementEditAction = mock(classOf()) + val edit = ElementEdit(1, elementEditType, pGeom(), "a", 1L, false, elementEditAction, true) elementEditsListener.onDeletedEdits(listOf(edit)) - verify(listener).onDecreased() + verifyInvokedExactlyOnce { listener.onDecreased() } } @Test fun `add synced element edit does not trigger listener`() { - val change = mock() - on(change.isSynced).thenReturn(true) + elementEditType = mock(classOf()) + elementEditAction = mock(classOf()) + val change = ElementEdit(1, elementEditType, pGeom(), "a", 1L, true, elementEditAction, true) elementEditsListener.onAddedEdit(change) - verifyNoInteractions(listener) } @Test fun `remove synced element edit does not trigger listener`() { - val edit = mock() - on(edit.isSynced).thenReturn(true) + elementEditType = mock(classOf()) + elementEditAction = mock(classOf()) + val edit = ElementEdit(1, elementEditType, pGeom(), "a", 1L, true, elementEditAction, true) elementEditsListener.onDeletedEdits(listOf(edit)) - verifyNoInteractions(listener) } @Test fun `add note edit triggers listener`() { noteEditsListener.onAddedEdit(noteEdit()) - verify(listener).onIncreased() + verifyInvokedExactlyOnce { listener.onIncreased() } } @Test fun `remove note edit triggers listener`() { noteEditsListener.onDeletedEdits(listOf(noteEdit())) - verify(listener).onDecreased() + verifyInvokedExactlyOnce { listener.onDecreased() } } @Test fun `marked note edit synced triggers listener`() { noteEditsListener.onSyncedEdit(noteEdit()) - verify(listener).onDecreased() + verifyInvokedExactlyOnce { listener.onDecreased() } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt index 1b41d19c52..fdcca7b7a6 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt @@ -12,17 +12,17 @@ import de.westnordost.streetcomplete.data.overlays.SelectedOverlaySource import de.westnordost.streetcomplete.data.visiblequests.TeamModeQuestFilter import de.westnordost.streetcomplete.data.visiblequests.VisibleQuestTypeSource import de.westnordost.streetcomplete.overlays.Overlay -import de.westnordost.streetcomplete.testutils.any import de.westnordost.streetcomplete.testutils.bbox -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.osmNoteQuest import de.westnordost.streetcomplete.testutils.osmQuest import de.westnordost.streetcomplete.testutils.osmQuestKey import de.westnordost.streetcomplete.testutils.pGeom -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -30,12 +30,12 @@ import kotlin.test.assertTrue class VisibleQuestsSourceTest { - private lateinit var osmQuestSource: OsmQuestSource + @Mock private lateinit var osmQuestSource: OsmQuestSource private lateinit var questTypeRegistry: QuestTypeRegistry - private lateinit var osmNoteQuestSource: OsmNoteQuestSource - private lateinit var visibleQuestTypeSource: VisibleQuestTypeSource - private lateinit var teamModeQuestFilter: TeamModeQuestFilter - private lateinit var selectedOverlaySource: SelectedOverlaySource + @Mock private lateinit var osmNoteQuestSource: OsmNoteQuestSource + @Mock private lateinit var visibleQuestTypeSource: VisibleQuestTypeSource + @Mock private lateinit var teamModeQuestFilter: TeamModeQuestFilter + @Mock private lateinit var selectedOverlaySource: SelectedOverlaySource private lateinit var source: VisibleQuestsSource private lateinit var noteQuestListener: OsmNoteQuestSource.Listener @@ -44,47 +44,47 @@ class VisibleQuestsSourceTest { private lateinit var teamModeListener: TeamModeQuestFilter.TeamModeChangeListener private lateinit var selectedOverlayListener: SelectedOverlaySource.Listener - private lateinit var listener: VisibleQuestsSource.Listener + @Mock private lateinit var listener: VisibleQuestsSource.Listener private val bbox = bbox(0.0, 0.0, 1.0, 1.0) private val questTypes = listOf(TestQuestTypeA(), TestQuestTypeB(), TestQuestTypeC()) private val questTypeNames = questTypes.map { it.name } @BeforeTest fun setUp() { - osmNoteQuestSource = mock() - osmQuestSource = mock() - visibleQuestTypeSource = mock() - teamModeQuestFilter = mock() - selectedOverlaySource = mock() + osmNoteQuestSource = mock(classOf()) + osmQuestSource = mock(classOf()) + visibleQuestTypeSource = mock(classOf()) + teamModeQuestFilter = mock(classOf()) + selectedOverlaySource = mock(classOf()) questTypeRegistry = QuestTypeRegistry(questTypes.mapIndexed { index, questType -> index to questType }) - on(visibleQuestTypeSource.isVisible(any())).thenReturn(true) - on(teamModeQuestFilter.isVisible(any())).thenReturn(true) + every { visibleQuestTypeSource.isVisible(any()) }.returns(true) + every { teamModeQuestFilter.isVisible(any()) }.returns(true) - on(osmNoteQuestSource.addListener(any())).then { invocation -> - noteQuestListener = (invocation.arguments[0] as OsmNoteQuestSource.Listener) + every { osmNoteQuestSource.addListener(any()) }.invokes { arguments -> + noteQuestListener = arguments[0] as OsmNoteQuestSource.Listener Unit } - on(osmQuestSource.addListener(any())).then { invocation -> - questListener = (invocation.arguments[0] as OsmQuestSource.Listener) + every { osmQuestSource.addListener(any()) }.invokes { arguments -> + questListener = arguments[0] as OsmQuestSource.Listener Unit } - on(visibleQuestTypeSource.addListener(any())).then { invocation -> - visibleQuestTypeListener = (invocation.arguments[0] as VisibleQuestTypeSource.Listener) + every { visibleQuestTypeSource.addListener(any()) }.invokes { arguments -> + visibleQuestTypeListener = arguments[0] as VisibleQuestTypeSource.Listener Unit } - on(teamModeQuestFilter.addListener(any())).then { invocation -> - teamModeListener = (invocation.arguments[0] as TeamModeQuestFilter.TeamModeChangeListener) + every { teamModeQuestFilter.addListener(any()) }.invokes { arguments -> + teamModeListener = arguments[0] as TeamModeQuestFilter.TeamModeChangeListener Unit } - on(selectedOverlaySource.addListener(any())).then { invocation -> - selectedOverlayListener = (invocation.arguments[0] as SelectedOverlaySource.Listener) + every { selectedOverlaySource.addListener(any()) }.invokes { arguments -> + selectedOverlayListener = arguments[0] as SelectedOverlaySource.Listener Unit } source = VisibleQuestsSource(questTypeRegistry, osmQuestSource, osmNoteQuestSource, visibleQuestTypeSource, teamModeQuestFilter, selectedOverlaySource) - listener = mock() + listener = mock(classOf()) source.addListener(listener) } @@ -92,8 +92,9 @@ class VisibleQuestsSourceTest { val bboxCacheWillRequest = bbox.asBoundingBoxOfEnclosingTiles(16) val osmQuests = questTypes.map { OsmQuest(it, ElementType.NODE, 1L, pGeom()) } val noteQuests = listOf(OsmNoteQuest(0L, LatLon(0.0, 0.0)), OsmNoteQuest(1L, LatLon(1.0, 1.0))) - on(osmQuestSource.getAllVisibleInBBox(bboxCacheWillRequest, questTypeNames)).thenReturn(osmQuests) - on(osmNoteQuestSource.getAllVisibleInBBox(bboxCacheWillRequest)).thenReturn(noteQuests) + every { osmQuestSource.getAllVisibleInBBox(bboxCacheWillRequest, questTypeNames) }.returns(osmQuests) + every { osmNoteQuestSource.getAllVisibleInBBox(bboxCacheWillRequest) }.returns(noteQuests) + every { selectedOverlaySource.selectedOverlay }.returns(null) val quests = source.getAllVisible(bbox) assertEquals(5, quests.size) @@ -102,23 +103,27 @@ class VisibleQuestsSourceTest { } @Test fun `getAllVisible does not return those that are invisible in team mode`() { - on(osmQuestSource.getAllVisibleInBBox(bbox, questTypeNames)).thenReturn(listOf(mock())) - on(osmNoteQuestSource.getAllVisibleInBBox(bbox)).thenReturn(listOf(mock())) - on(teamModeQuestFilter.isVisible(any())).thenReturn(false) - on(teamModeQuestFilter.isEnabled).thenReturn(true) + val bboxCacheWillRequest = bbox.asBoundingBoxOfEnclosingTiles(16) + val osmQuests = questTypes.map { OsmQuest(it, ElementType.NODE, 1L, pGeom()) } + val noteQuests = listOf(OsmNoteQuest(0L, LatLon(0.0, 0.0)), OsmNoteQuest(1L, LatLon(1.0, 1.0))) + every { osmQuestSource.getAllVisibleInBBox(bboxCacheWillRequest, questTypeNames) }.returns(osmQuests) + every { osmNoteQuestSource.getAllVisibleInBBox(bboxCacheWillRequest) }.returns(noteQuests) + every { teamModeQuestFilter.isVisible(any()) }.returns(false) + every { teamModeQuestFilter.isEnabled }.returns(true) + every { selectedOverlaySource.selectedOverlay }.returns(null) val quests = source.getAllVisible(bbox) assertTrue(quests.isEmpty()) } @Test fun `getAllVisible does not return those that are invisible because of an overlay`() { - on(osmQuestSource.getAllVisibleInBBox(bbox.asBoundingBoxOfEnclosingTiles(16), listOf("TestQuestTypeA"))) - .thenReturn(listOf(OsmQuest(TestQuestTypeA(), ElementType.NODE, 1, ElementPointGeometry(bbox.min)))) - on(osmNoteQuestSource.getAllVisibleInBBox(bbox.asBoundingBoxOfEnclosingTiles(16))).thenReturn(listOf()) + every { osmQuestSource.getAllVisibleInBBox(bbox.asBoundingBoxOfEnclosingTiles(16), listOf("TestQuestTypeA")) } + .returns(listOf(OsmQuest(TestQuestTypeA(), ElementType.NODE, 1, ElementPointGeometry(bbox.min)))) + every { osmNoteQuestSource.getAllVisibleInBBox(bbox.asBoundingBoxOfEnclosingTiles(16)) }.returns(listOf()) - val overlay: Overlay = mock() - on(overlay.hidesQuestTypes).thenReturn(setOf("TestQuestTypeB", "TestQuestTypeC")) - on(selectedOverlaySource.selectedOverlay).thenReturn(overlay) + val overlay: Overlay = mock(classOf()) + every { overlay.hidesQuestTypes }.returns(setOf("TestQuestTypeB", "TestQuestTypeC")) + every { selectedOverlaySource.selectedOverlay }.returns(overlay) val quests = source.getAllVisible(bbox) assertEquals(1, quests.size) @@ -128,37 +133,37 @@ class VisibleQuestsSourceTest { val quests = listOf(osmQuest(elementId = 1), osmQuest(elementId = 2)) val deleted = listOf(osmQuestKey(elementId = 3), osmQuestKey(elementId = 4)) questListener.onUpdated(quests, deleted) - verify(listener).onUpdatedVisibleQuests(eq(quests), eq(deleted)) + verifyInvokedExactlyOnce { listener.onUpdatedVisibleQuests(quests, deleted) } } @Test fun `osm quests added of invisible type does not trigger listener`() { val quests = listOf(osmQuest(elementId = 1), osmQuest(elementId = 2)) - on(visibleQuestTypeSource.isVisible(any())).thenReturn(false) + every { visibleQuestTypeSource.isVisible(any()) }.returns(false) questListener.onUpdated(quests, emptyList()) - verifyNoInteractions(listener) + // verifyNoInteractions(listener) } @Test fun `osm note quests added or removed triggers listener`() { val quests = listOf(osmNoteQuest(1L), osmNoteQuest(2L)) val deleted = listOf(OsmNoteQuestKey(3), OsmNoteQuestKey(4)) noteQuestListener.onUpdated(quests, listOf(3L, 4L)) - verify(listener).onUpdatedVisibleQuests(eq(quests), eq(deleted)) + verifyInvokedExactlyOnce { listener.onUpdatedVisibleQuests(quests, deleted) } } @Test fun `osm note quests added of invisible type does not trigger listener`() { val quests = listOf(osmNoteQuest(1L), osmNoteQuest(2L)) - on(visibleQuestTypeSource.isVisible(any())).thenReturn(false) + every { visibleQuestTypeSource.isVisible(any()) }.returns(false) noteQuestListener.onUpdated(quests, emptyList()) - verifyNoInteractions(listener) + // verifyNoInteractions(listener) } @Test fun `trigger invalidate listener if quest type visibilities changed`() { visibleQuestTypeListener.onQuestTypeVisibilitiesChanged() - verify(listener).onVisibleQuestsInvalidated() + verifyInvokedExactlyOnce { listener.onVisibleQuestsInvalidated() } } @Test fun `trigger invalidate listener if visible note quests were invalidated`() { noteQuestListener.onInvalidated() - verify(listener).onVisibleQuestsInvalidated() + verifyInvokedExactlyOnce { listener.onVisibleQuestsInvalidated() } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsControllerTest.kt index 4a01f41628..2f75fc7286 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/user/achievements/AchievementsControllerTest.kt @@ -11,20 +11,21 @@ import de.westnordost.streetcomplete.overlays.AbstractOverlayForm import de.westnordost.streetcomplete.overlays.Overlay import de.westnordost.streetcomplete.overlays.Style import de.westnordost.streetcomplete.quests.AbstractQuestForm -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class AchievementsControllerTest { - private lateinit var userAchievementsDao: UserAchievementsDao - private lateinit var userLinksDao: UserLinksDao - private lateinit var statisticsSource: StatisticsSource + @Mock private lateinit var userAchievementsDao: UserAchievementsDao + @Mock private lateinit var userLinksDao: UserLinksDao + @Mock private lateinit var statisticsSource: StatisticsSource private lateinit var allEditTypes: AllEditTypes private var allLinks: List = listOf() private var allAchievements: List = listOf() @@ -32,23 +33,28 @@ class AchievementsControllerTest { private lateinit var statisticsListener: StatisticsSource.Listener private lateinit var listener: AchievementsSource.Listener + // dummy + @Mock private lateinit var form: AbstractQuestForm + @Mock private lateinit var editTypeAchievement: EditTypeAchievement + @BeforeTest fun setUp() { - userAchievementsDao = mock() - on(userAchievementsDao.getAll()).thenReturn(mapOf()) - userLinksDao = mock() - statisticsSource = mock() + form = mock(classOf()) + userAchievementsDao = mock(classOf()) + every { userAchievementsDao.getAll() }.returns(mapOf()) + userLinksDao = mock(classOf()) + statisticsSource = mock(classOf()) allEditTypes = AllEditTypes(listOf( QuestTypeRegistry(listOf(0 to QuestOne, 1 to QuestTwo)), OverlayRegistry(listOf(0 to OverlayOne)), )) - listener = mock() + listener = mock(classOf()) allLinks = listOf() allAchievements = listOf() - on(statisticsSource.addListener(any())).then { invocation -> - statisticsListener = invocation.getArgument(0) + every { statisticsSource.addListener(any()) }.invokes { arguments -> + statisticsListener = arguments as StatisticsSource.Listener Unit } } @@ -60,51 +66,51 @@ class AchievementsControllerTest { ).also { it.addListener(listener) } @Test fun `unlocks DaysActive achievement`() { - on(statisticsSource.daysActive).thenReturn(1) + every { statisticsSource.daysActive }.returns(1) val achievement = achievement("daysActive", DaysActive) allAchievements = listOf(achievement) createAchievementsController() statisticsListener.onUpdatedDaysActive() - verify(userAchievementsDao).put("daysActive", 1) - verify(listener).onAchievementUnlocked(achievement, 1) + verifyInvokedExactlyOnce { userAchievementsDao.put("daysActive", 1) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 1) } } @Test fun `unlocks TotalEditCount achievement`() { - on(statisticsSource.getEditCount()).thenReturn(1, 2) + every { statisticsSource.getEditCount() }.returnsMany(1, 2) val achievement = achievement("allQuests", TotalEditCount) allAchievements = listOf(achievement) createAchievementsController() statisticsListener.onAddedOne("QuestOne") - verify(userAchievementsDao).put("allQuests", 1) - verify(listener).onAchievementUnlocked(achievement, 1) + verifyInvokedExactlyOnce { userAchievementsDao.put("allQuests", 1) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 1) } statisticsListener.onAddedOne("OverlayOne") - verify(userAchievementsDao).put("allQuests", 2) - verify(listener).onAchievementUnlocked(achievement, 2) + verifyInvokedExactlyOnce { userAchievementsDao.put("allQuests", 2) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 2) } } @Test fun `unlocks EditsOfTypeCount achievement`() { - on(statisticsSource.getEditCount(listOf("QuestOne", "QuestTwo", "OverlayOne"))).thenReturn(1, 2) + every { statisticsSource.getEditCount(listOf("QuestOne", "QuestTwo", "OverlayOne")) }.returnsMany(1, 2) val achievement = achievement("mixedAchievement", EditsOfTypeCount) allAchievements = listOf(achievement) createAchievementsController() statisticsListener.onAddedOne("QuestTwo") - verify(userAchievementsDao).put("mixedAchievement", 1) - verify(listener).onAchievementUnlocked(achievement, 1) + verifyInvokedExactlyOnce { userAchievementsDao.put("mixedAchievement", 1) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 1) } statisticsListener.onAddedOne("OverlayOne") - verify(userAchievementsDao).put("mixedAchievement", 2) - verify(listener).onAchievementUnlocked(achievement, 2) + verifyInvokedExactlyOnce { userAchievementsDao.put("mixedAchievement", 2) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 2) } } @Test fun `unlocks multiple locked levels of an achievement and grants those links`() { - on(userAchievementsDao.getAll()).thenReturn(mapOf("allQuests" to 2)) - on(statisticsSource.getEditCount()).thenReturn(5) + every { userAchievementsDao.getAll() }.returns(mapOf("allQuests" to 2)) + every { statisticsSource.getEditCount() }.returns(5) val achievement = achievement( id = "allQuests", condition = TotalEditCount, @@ -121,15 +127,15 @@ class AchievementsControllerTest { createAchievementsController() statisticsListener.onAddedOne("QuestOne") - verify(userAchievementsDao).put("allQuests", 5) - verify(userLinksDao).addAll(listOf("d", "e", "f")) - verify(listener).onAchievementUnlocked(achievement, 3) - verify(listener).onAchievementUnlocked(achievement, 4) - verify(listener).onAchievementUnlocked(achievement, 5) + verifyInvokedExactlyOnce { userAchievementsDao.put("allQuests", 5) } + verifyInvokedExactlyOnce { userLinksDao.addAll(listOf("d", "e", "f")) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 3) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 4) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 5) } } @Test fun `unlocks links not yet unlocked`() { - on(userAchievementsDao.getAll()).thenReturn(mapOf("allQuests" to 2)) + every { userAchievementsDao.getAll() }.returns(mapOf("allQuests" to 2)) allAchievements = listOf(achievement( id = "allQuests", condition = TotalEditCount, @@ -143,11 +149,11 @@ class AchievementsControllerTest { createAchievementsController() statisticsListener.onUpdatedAll() - verify(userLinksDao).addAll(listOf("a", "b", "c")) + verifyInvokedExactlyOnce { userLinksDao.addAll(listOf("a", "b", "c")) } } @Test fun `no achievement level above maxLevel will be granted`() { - on(statisticsSource.daysActive).thenReturn(100) + every { statisticsSource.daysActive }.returns(100) val achievement = achievement( id = "daysActive", condition = DaysActive, @@ -158,15 +164,15 @@ class AchievementsControllerTest { createAchievementsController() statisticsListener.onUpdatedDaysActive() - verify(userAchievementsDao).put("daysActive", 5) - verify(listener).onAchievementUnlocked(achievement, 5) + verifyInvokedExactlyOnce { userAchievementsDao.put("daysActive", 5) } + verifyInvokedExactlyOnce { listener.onAchievementUnlocked(achievement, 5) } } @Test fun `only updates achievements for given questType`() { // all achievements below should usually be granted - on(statisticsSource.daysActive).thenReturn(1) - on(statisticsSource.getEditCount(any>())).thenReturn(1) - on(statisticsSource.getEditCount()).thenReturn(1) + every { statisticsSource.daysActive }.returns(1) + every { statisticsSource.getEditCount(any>()) }.returns(1) + every { statisticsSource.getEditCount() }.returns(1) allAchievements = listOf( achievement("daysActive", DaysActive), @@ -179,17 +185,17 @@ class AchievementsControllerTest { createAchievementsController() statisticsListener.onAddedOne("QuestOne") - verify(userAchievementsDao).getAll() - verify(userAchievementsDao).put("thisAchievement", 1) - verify(userAchievementsDao).put("mixedAchievement", 1) - verify(userAchievementsDao).put("allQuests", 1) - verifyNoMoreInteractions(userAchievementsDao) + verifyInvokedExactlyOnce { userAchievementsDao.getAll() } + verifyInvokedExactlyOnce { userAchievementsDao.put("thisAchievement", 1) } + verifyInvokedExactlyOnce { userAchievementsDao.put("mixedAchievement", 1) } + verifyInvokedExactlyOnce { userAchievementsDao.put("allQuests", 1) } + // verifyNoMoreInteractions(userAchievementsDao) } @Test fun `only updates daysActive achievements`() { - on(statisticsSource.daysActive).thenReturn(1) - on(statisticsSource.getEditCount(any>())).thenReturn(1) - on(statisticsSource.getEditCount()).thenReturn(1) + every { statisticsSource.daysActive }.returns(1) + every { statisticsSource.getEditCount(any>()) }.returns(1) + every { statisticsSource.getEditCount() }.returns(1) allAchievements = listOf( achievement("daysActive", DaysActive), @@ -201,24 +207,24 @@ class AchievementsControllerTest { createAchievementsController() statisticsListener.onUpdatedDaysActive() - verify(userAchievementsDao).getAll() - verify(userAchievementsDao).put("daysActive", 1) - verify(userAchievementsDao).put("daysActive2", 1) - verifyNoMoreInteractions(userAchievementsDao) + verifyInvokedExactlyOnce { userAchievementsDao.getAll() } + verifyInvokedExactlyOnce { userAchievementsDao.put("daysActive", 1) } + verifyInvokedExactlyOnce { userAchievementsDao.put("daysActive2", 1) } + // verifyNoMoreInteractions(userAchievementsDao) } @Test fun `clears all achievements on clearing statistics`() { createAchievementsController() statisticsListener.onCleared() - verify(userAchievementsDao).clear() - verify(userLinksDao).clear() - verify(listener).onAllAchievementsUpdated() + verifyInvokedExactlyOnce { userAchievementsDao.clear() } + verifyInvokedExactlyOnce { userLinksDao.clear() } + verifyInvokedExactlyOnce { listener.onAllAchievementsUpdated() } } @Test fun `get all unlocked links`() { allLinks = links("a", "b", "c") - on(userLinksDao.getAll()).thenReturn(listOf("a", "b")) + every { userLinksDao.getAll() }.returns(listOf("a", "b")) assertEquals( links("a", "b"), createAchievementsController().getLinks() @@ -230,7 +236,7 @@ class AchievementsControllerTest { achievement("daysActive", DaysActive), achievement("otherAchievement", EditsOfTypeCount) ) - on(userAchievementsDao.getAll()).thenReturn(mapOf( + every { userAchievementsDao.getAll() }.returns(mapOf( "daysActive" to 3 )) assertEquals( @@ -254,8 +260,9 @@ private fun links(vararg ids: String): List = private fun editTypeAchievements(achievementIds: List): List = achievementIds.map { - val editTypeAchievement: EditTypeAchievement = mock() - on(editTypeAchievement.id).thenReturn(it) + // todo can't mock enums + val editTypeAchievement: EditTypeAchievement = mock(classOf()) + every { editTypeAchievement.id }.returns(it) editTypeAchievement } @@ -263,7 +270,7 @@ private object QuestOne : QuestType { override val icon = 0 override val title = 0 override val wikiLink: String? = null - override fun createForm(): AbstractQuestForm = mock() + override fun createForm(): AbstractQuestForm = mock(classOf()) override val achievements = editTypeAchievements(listOf("thisAchievement", "mixedAchievement")) } @@ -271,7 +278,7 @@ private object QuestTwo : QuestType { override val icon = 0 override val title = 0 override val wikiLink: String? = null - override fun createForm(): AbstractQuestForm = mock() + override fun createForm(): AbstractQuestForm = mock(classOf()) override val achievements = editTypeAchievements(listOf("otherAchievement", "mixedAchievement")) } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsControllerTest.kt index 69c94b9559..e402c70973 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsControllerTest.kt @@ -4,48 +4,50 @@ import com.russhwolf.settings.ObservableSettings import de.westnordost.countryboundaries.CountryBoundaries import de.westnordost.streetcomplete.Prefs import de.westnordost.streetcomplete.data.user.UserLoginStatusSource -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.p +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlinx.datetime.LocalDate -import org.mockito.ArgumentMatchers.anyDouble -import org.mockito.Mockito.verify import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class StatisticsControllerTest { - private lateinit var editTypeStatisticsDao: EditTypeStatisticsDao - private lateinit var countryStatisticsDao: CountryStatisticsDao - private lateinit var currentWeekEditTypeStatisticsDao: EditTypeStatisticsDao - private lateinit var currentWeekCountryStatisticsDao: CountryStatisticsDao - private lateinit var activeDatesDao: ActiveDatesDao + @Mock private lateinit var editTypeStatisticsDao: EditTypeStatisticsDao + @Mock private lateinit var countryStatisticsDao: CountryStatisticsDao + @Mock private lateinit var currentWeekEditTypeStatisticsDao: EditTypeStatisticsDao + @Mock private lateinit var currentWeekCountryStatisticsDao: CountryStatisticsDao + @Mock private lateinit var activeDatesDao: ActiveDatesDao + // todo: not mockable because it's from a library private lateinit var countryBoundaries: CountryBoundaries - private lateinit var prefs: ObservableSettings - private lateinit var loginStatusSource: UserLoginStatusSource - private lateinit var loginStatusListener: UserLoginStatusSource.Listener + @Mock private lateinit var prefs: ObservableSettings + @Mock private lateinit var loginStatusSource: UserLoginStatusSource + @Mock private lateinit var loginStatusListener: UserLoginStatusSource.Listener private lateinit var statisticsController: StatisticsController - private lateinit var listener: StatisticsSource.Listener + @Mock private lateinit var listener: StatisticsSource.Listener private val questA = "TestQuestTypeA" private val questB = "TestQuestTypeB" @BeforeTest fun setUp() { - editTypeStatisticsDao = mock() - countryStatisticsDao = mock() - currentWeekEditTypeStatisticsDao = mock() - currentWeekCountryStatisticsDao = mock() - activeDatesDao = mock() - countryBoundaries = mock() - prefs = mock() - listener = mock() - loginStatusSource = mock() - - on(loginStatusSource.addListener(any())).then { invocation -> - loginStatusListener = invocation.getArgument(0) + editTypeStatisticsDao = mock(classOf()) + countryStatisticsDao = mock(classOf()) + currentWeekEditTypeStatisticsDao = mock(classOf()) + currentWeekCountryStatisticsDao = mock(classOf()) + activeDatesDao = mock(classOf()) + countryBoundaries = mock(classOf()) + prefs = mock(classOf()) + listener = mock(classOf()) + loginStatusSource = mock(classOf()) + + every { loginStatusSource.addListener(any()) }.invokes { arguments -> + loginStatusListener = arguments[0] as UserLoginStatusSource.Listener Unit } @@ -59,7 +61,7 @@ class StatisticsControllerTest { } @Test fun getSolvedCount() { - on(editTypeStatisticsDao.getTotalAmount()).thenReturn(5) + every { editTypeStatisticsDao.getTotalAmount() }.returns(5) assertEquals( 5, statisticsController.getEditCount() @@ -67,67 +69,67 @@ class StatisticsControllerTest { } @Test fun `adding one`() { - on(countryBoundaries.getIds(anyDouble(), anyDouble())).thenReturn(listOf("US-TX", "US", "World")) + every { countryBoundaries.getIds(any(), any()) }.returns(listOf("US-TX", "US", "World")) statisticsController.addOne(questA, p(0.0, 0.0)) - verify(editTypeStatisticsDao).addOne("TestQuestTypeA") - verify(countryStatisticsDao).addOne("US") - verify(currentWeekEditTypeStatisticsDao).addOne("TestQuestTypeA") - verify(currentWeekCountryStatisticsDao).addOne("US") - verify(activeDatesDao).addToday() - verify(listener).onAddedOne(questA) + verifyInvokedExactlyOnce { editTypeStatisticsDao.addOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { countryStatisticsDao.addOne("US") } + verifyInvokedExactlyOnce { currentWeekEditTypeStatisticsDao.addOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { currentWeekCountryStatisticsDao.addOne("US") } + verifyInvokedExactlyOnce { activeDatesDao.addToday() } + verifyInvokedExactlyOnce { listener.onAddedOne(questA) } } @Test fun `adding one one day later`() { - on(prefs.getInt(Prefs.USER_LAST_TIMESTAMP_ACTIVE, 0)).thenReturn(0) + every { prefs.getInt(Prefs.USER_LAST_TIMESTAMP_ACTIVE, 0) }.returns(0) statisticsController.addOne(questA, p(0.0, 0.0)) - verify(editTypeStatisticsDao).addOne("TestQuestTypeA") - verify(currentWeekEditTypeStatisticsDao).addOne("TestQuestTypeA") - verify(activeDatesDao).addToday() - verify(listener).onAddedOne(questA) - verify(listener).onUpdatedDaysActive() + verifyInvokedExactlyOnce { editTypeStatisticsDao.addOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { currentWeekEditTypeStatisticsDao.addOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { activeDatesDao.addToday() } + verifyInvokedExactlyOnce { listener.onAddedOne(questA) } + verifyInvokedExactlyOnce { listener.onUpdatedDaysActive() } } @Test fun `subtracting one`() { - on(countryBoundaries.getIds(anyDouble(), anyDouble())).thenReturn(listOf("US-TX", "US", "World")) + every { countryBoundaries.getIds(any(), any()) }.returns(listOf("US-TX", "US", "World")) statisticsController.subtractOne(questA, p(0.0, 0.0)) - verify(editTypeStatisticsDao).subtractOne("TestQuestTypeA") - verify(countryStatisticsDao).subtractOne("US") - verify(currentWeekEditTypeStatisticsDao).subtractOne("TestQuestTypeA") - verify(currentWeekCountryStatisticsDao).subtractOne("US") - verify(activeDatesDao).addToday() - verify(listener).onSubtractedOne(questA) + verifyInvokedExactlyOnce { editTypeStatisticsDao.subtractOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { countryStatisticsDao.subtractOne("US") } + verifyInvokedExactlyOnce { currentWeekEditTypeStatisticsDao.subtractOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { currentWeekCountryStatisticsDao.subtractOne("US") } + verifyInvokedExactlyOnce { activeDatesDao.addToday() } + verifyInvokedExactlyOnce { listener.onSubtractedOne(questA) } } @Test fun `subtracting one one day later`() { - on(prefs.getInt(Prefs.USER_LAST_TIMESTAMP_ACTIVE, 0)).thenReturn(0) + every { prefs.getInt(Prefs.USER_LAST_TIMESTAMP_ACTIVE, 0) }.returns(0) statisticsController.subtractOne(questA, p(0.0, 0.0)) - verify(editTypeStatisticsDao).subtractOne("TestQuestTypeA") - verify(currentWeekEditTypeStatisticsDao).subtractOne("TestQuestTypeA") - verify(activeDatesDao).addToday() - verify(listener).onSubtractedOne(questA) - verify(listener).onUpdatedDaysActive() + verifyInvokedExactlyOnce { editTypeStatisticsDao.subtractOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { currentWeekEditTypeStatisticsDao.subtractOne("TestQuestTypeA") } + verifyInvokedExactlyOnce { activeDatesDao.addToday() } + verifyInvokedExactlyOnce { listener.onSubtractedOne(questA) } + verifyInvokedExactlyOnce { listener.onUpdatedDaysActive() } } @Test fun `clear all`() { loginStatusListener.onLoggedOut() - verify(editTypeStatisticsDao).clear() - verify(countryStatisticsDao).clear() - verify(currentWeekCountryStatisticsDao).clear() - verify(currentWeekEditTypeStatisticsDao).clear() - verify(activeDatesDao).clear() - verify(prefs).remove(Prefs.USER_DAYS_ACTIVE) - verify(prefs).remove(Prefs.IS_SYNCHRONIZING_STATISTICS) - verify(prefs).remove(Prefs.USER_GLOBAL_RANK) - verify(prefs).remove(Prefs.USER_GLOBAL_RANK_CURRENT_WEEK) - verify(prefs).remove(Prefs.USER_LAST_TIMESTAMP_ACTIVE) - verify(listener).onCleared() + verifyInvokedExactlyOnce { editTypeStatisticsDao.clear() } + verifyInvokedExactlyOnce { countryStatisticsDao.clear() } + verifyInvokedExactlyOnce { currentWeekCountryStatisticsDao.clear() } + verifyInvokedExactlyOnce { currentWeekEditTypeStatisticsDao.clear() } + verifyInvokedExactlyOnce { activeDatesDao.clear() } + verifyInvokedExactlyOnce { prefs.remove(Prefs.USER_DAYS_ACTIVE) } + verifyInvokedExactlyOnce { prefs.remove(Prefs.IS_SYNCHRONIZING_STATISTICS) } + verifyInvokedExactlyOnce { prefs.remove(Prefs.USER_GLOBAL_RANK) } + verifyInvokedExactlyOnce { prefs.remove(Prefs.USER_GLOBAL_RANK_CURRENT_WEEK) } + verifyInvokedExactlyOnce { prefs.remove(Prefs.USER_LAST_TIMESTAMP_ACTIVE) } + verifyInvokedExactlyOnce { listener.onCleared() } } @Test fun `update all`() { @@ -159,32 +161,32 @@ class StatisticsControllerTest { lastUpdate = 9999999, isAnalyzing = false )) - verify(editTypeStatisticsDao).replaceAll(mapOf( + verifyInvokedExactlyOnce { editTypeStatisticsDao.replaceAll(mapOf( "TestQuestTypeA" to 123, "TestQuestTypeB" to 44 - )) - verify(countryStatisticsDao).replaceAll(listOf( + )) } + verifyInvokedExactlyOnce { countryStatisticsDao.replaceAll(listOf( CountryStatistics("DE", 12, 5), CountryStatistics("US", 43, null), - )) - verify(currentWeekEditTypeStatisticsDao).replaceAll(mapOf( + )) } + verifyInvokedExactlyOnce { currentWeekEditTypeStatisticsDao.replaceAll(mapOf( "TestQuestTypeA" to 321, "TestQuestTypeB" to 33 - )) - verify(currentWeekCountryStatisticsDao).replaceAll(listOf( + )) } + verifyInvokedExactlyOnce { currentWeekCountryStatisticsDao.replaceAll(listOf( CountryStatistics("AT", 999, 88), CountryStatistics("IT", 99, null), - )) - verify(activeDatesDao).replaceAll(listOf( + )) } + verifyInvokedExactlyOnce { activeDatesDao.replaceAll(listOf( LocalDate.parse("1999-04-08"), LocalDate.parse("1888-01-02") - )) - verify(prefs).putInt(Prefs.ACTIVE_DATES_RANGE, 12) - verify(prefs).putInt(Prefs.USER_DAYS_ACTIVE, 333) - verify(prefs).putBoolean(Prefs.IS_SYNCHRONIZING_STATISTICS, false) - verify(prefs).putInt(Prefs.USER_GLOBAL_RANK, 999) - verify(prefs).putInt(Prefs.USER_GLOBAL_RANK_CURRENT_WEEK, 111) - verify(prefs).putLong(Prefs.USER_LAST_TIMESTAMP_ACTIVE, 9999999) - verify(listener).onUpdatedAll() + )) } + verifyInvokedExactlyOnce { prefs.putInt(Prefs.ACTIVE_DATES_RANGE, 12) } + verifyInvokedExactlyOnce { prefs.putInt(Prefs.USER_DAYS_ACTIVE, 333) } + verifyInvokedExactlyOnce { prefs.putBoolean(Prefs.IS_SYNCHRONIZING_STATISTICS, false) } + verifyInvokedExactlyOnce { prefs.putInt(Prefs.USER_GLOBAL_RANK, 999) } + verifyInvokedExactlyOnce { prefs.putInt(Prefs.USER_GLOBAL_RANK_CURRENT_WEEK, 111) } + verifyInvokedExactlyOnce { prefs.putLong(Prefs.USER_LAST_TIMESTAMP_ACTIVE, 9999999) } + verifyInvokedExactlyOnce { listener.onUpdatedAll() } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsDownloaderTest.kt index 03ec69519c..39e8f1a9f1 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/user/statistics/StatisticsDownloaderTest.kt @@ -1,24 +1,28 @@ package de.westnordost.streetcomplete.data.user.statistics -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respondBadRequest import io.ktor.client.engine.mock.respondOk +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails class StatisticsDownloaderTest { - private val statisticsParser: StatisticsParser = mock() + @Mock + private val statisticsParser: StatisticsParser = mock(classOf()) private val validResponseMockEngine = MockEngine { _ -> respondOk("simple response") } @Test fun `download parses all statistics`() = runBlocking { val stats = Statistics(types = listOf(), countries = listOf(), rank = 2, daysActive = 100, currentWeekRank = 50, currentWeekTypes = listOf(), currentWeekCountries = listOf(), activeDates = listOf(), activeDatesRange = 100, isAnalyzing = false, lastUpdate = 10) - on(statisticsParser.parse("simple response")).thenReturn(stats) + every { statisticsParser.parse("simple response") }.returns(stats) assertEquals(stats, StatisticsDownloader(HttpClient(validResponseMockEngine), "", statisticsParser).download(100)) } @@ -33,6 +37,9 @@ class StatisticsDownloaderTest { } @Test fun `download constructs request URL`() = runBlocking { + val stats = Statistics(types = listOf(), countries = listOf(), rank = 2, daysActive = 100, currentWeekRank = 50, currentWeekTypes = listOf(), currentWeekCountries = listOf(), activeDates = listOf(), activeDatesRange = 100, isAnalyzing = false, lastUpdate = 10) + every { statisticsParser.parse(any()) }.returns(stats) + StatisticsDownloader( HttpClient(validResponseMockEngine), "https://example.com/stats/", diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetControllerTest.kt index f080f73e98..8feea6b8d7 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestPresetControllerTest.kt @@ -1,66 +1,69 @@ package de.westnordost.streetcomplete.data.visiblequests -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on -import org.mockito.Mockito.verify +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class QuestPresetControllerTest { - private lateinit var questPresetsDao: QuestPresetsDao - private lateinit var selectedQuestPresetStore: SelectedQuestPresetStore + @Mock private lateinit var questPresetsDao: QuestPresetsDao + @Mock private lateinit var selectedQuestPresetStore: SelectedQuestPresetStore private lateinit var ctrl: QuestPresetsController - private lateinit var listener: QuestPresetsSource.Listener + @Mock private lateinit var listener: QuestPresetsSource.Listener private val preset = QuestPreset(1, "test") @BeforeTest fun setUp() { - questPresetsDao = mock() - selectedQuestPresetStore = mock() + questPresetsDao = mock(classOf()) + selectedQuestPresetStore = mock(classOf()) ctrl = QuestPresetsController(questPresetsDao, selectedQuestPresetStore) - listener = mock() + listener = mock(classOf()) ctrl.addListener(listener) } @Test fun get() { - on(questPresetsDao.getName(1)).thenReturn("huhu") - on(selectedQuestPresetStore.get()).thenReturn(1) +every { questPresetsDao.getName(1) }.returns("huhu") +every { selectedQuestPresetStore.get() }.returns(1) assertEquals("huhu", ctrl.selectedQuestPresetName) } @Test fun getAll() { - on(questPresetsDao.getAll()).thenReturn(listOf(preset)) +every { questPresetsDao.getAll() }.returns(listOf(preset)) assertEquals(listOf(preset), ctrl.getAll()) } @Test fun add() { - on(questPresetsDao.add(any())).thenReturn(123) +every { questPresetsDao.add(any()) }.returns(123) ctrl.add("test") - verify(questPresetsDao).add("test") - verify(listener).onAddedQuestPreset(QuestPreset(123, "test")) +verifyInvokedExactlyOnce { questPresetsDao.add("test") } +verifyInvokedExactlyOnce { listener.onAddedQuestPreset(QuestPreset(123, "test")) } } @Test fun delete() { + every { selectedQuestPresetStore.get() }.returns(123) ctrl.delete(123) - verify(questPresetsDao).delete(123) - verify(listener).onDeletedQuestPreset(123) +verifyInvokedExactlyOnce { questPresetsDao.delete(123) } +verifyInvokedExactlyOnce { listener.onDeletedQuestPreset(123) } } @Test fun `delete current preset switches to preset 0`() { - on(ctrl.selectedId).thenReturn(55) +every { ctrl.selectedId }.returns(55) ctrl.delete(55) - verify(questPresetsDao).delete(55) - verify(listener).onDeletedQuestPreset(55) - verify(selectedQuestPresetStore).set(0) +verifyInvokedExactlyOnce { questPresetsDao.delete(55) } +verifyInvokedExactlyOnce { listener.onDeletedQuestPreset(55) } +verifyInvokedExactlyOnce { selectedQuestPresetStore.set(0) } } @Test fun `change current preset`() { ctrl.selectedId = 11 - verify(selectedQuestPresetStore).set(11) - verify(listener).onSelectedQuestPresetChanged() +verifyInvokedExactlyOnce { selectedQuestPresetStore.set(11) } +verifyInvokedExactlyOnce { listener.onSelectedQuestPresetChanged() } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderControllerTest.kt index 5399d01399..781773240d 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/QuestTypeOrderControllerTest.kt @@ -6,22 +6,23 @@ import de.westnordost.streetcomplete.data.quest.TestQuestTypeA import de.westnordost.streetcomplete.data.quest.TestQuestTypeB import de.westnordost.streetcomplete.data.quest.TestQuestTypeC import de.westnordost.streetcomplete.data.quest.TestQuestTypeD -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.on -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.mock import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals class QuestTypeOrderControllerTest { - private lateinit var questTypeOrderDao: QuestTypeOrderDao - private lateinit var questPresetsSource: QuestPresetsSource + @Mock private lateinit var questTypeOrderDao: QuestTypeOrderDao + @Mock private lateinit var questPresetsSource: QuestPresetsSource private lateinit var questTypeRegistry: QuestTypeRegistry private lateinit var ctrl: QuestTypeOrderController - private lateinit var listener: QuestTypeOrderSource.Listener + @Mock private lateinit var listener: QuestTypeOrderSource.Listener private lateinit var questPresetsListener: QuestPresetsSource.Listener @@ -31,8 +32,8 @@ class QuestTypeOrderControllerTest { private val questD = TestQuestTypeD() @BeforeTest fun setUp() { - questTypeOrderDao = mock() - questPresetsSource = mock() + questTypeOrderDao = mock(classOf()) + questPresetsSource = mock(classOf()) questTypeRegistry = QuestTypeRegistry(listOf( 0 to questA, 1 to questB, @@ -40,27 +41,27 @@ class QuestTypeOrderControllerTest { 3 to questD )) - on(questPresetsSource.addListener(any())).then { invocation -> - questPresetsListener = (invocation.arguments[0] as QuestPresetsSource.Listener) + on { questPresetsSource.addListener(any()) }.invokes { arguments -> + questPresetsListener = arguments[0] as QuestPresetsSource.Listener Unit } - on(questPresetsSource.selectedId).thenReturn(0) + on { questPresetsSource.selectedId }.returns(0) ctrl = QuestTypeOrderController(questTypeOrderDao, questPresetsSource, questTypeRegistry) - listener = mock() + listener = mock(classOf()) ctrl.addListener(listener) } @Test fun `notifies listener when changing quest preset`() { questPresetsListener.onSelectedQuestPresetChanged() - verify(listener).onQuestTypeOrdersChanged() + verifyInvokedExactlyOnce { listener.onQuestTypeOrdersChanged() } } @Test fun sort() { val list = mutableListOf(questA, questB, questC, questD) - on(questTypeOrderDao.getAll(0)).thenReturn(listOf( + on { questTypeOrderDao.getAll(0) }.returns(listOf( // A,B,C,D -> A,D,B,C questD.name to questA.name, // A,D,B,C -> A,D,C,B @@ -77,7 +78,7 @@ class QuestTypeOrderControllerTest { } @Test fun getOrders() { - on(questTypeOrderDao.getAll(0)).thenReturn(listOf( + on { questTypeOrderDao.getAll(0) }.returns(listOf( questA.name to questB.name, questC.name to questD.name )) @@ -89,43 +90,43 @@ class QuestTypeOrderControllerTest { @Test fun setOrders() { ctrl.setOrders(listOf(questA to questB, questC to questD)) - verify(questTypeOrderDao).setAll(0, listOf( + verifyInvokedExactlyOnce { questTypeOrderDao.setAll(0, listOf( questA.name to questB.name, questC.name to questD.name - )) - verify(listener).onQuestTypeOrdersChanged() + )) } + verifyInvokedExactlyOnce { listener.onQuestTypeOrdersChanged() } } @Test fun `setOrders on not selected preset`() { ctrl.setOrders(listOf(questA to questB, questC to questD), 1) - verify(questTypeOrderDao).setAll(1, listOf( + verifyInvokedExactlyOnce { questTypeOrderDao.setAll(1, listOf( questA.name to questB.name, questC.name to questD.name - )) - verifyNoInteractions(listener) + ) ) } + // verifyNoInteractions(listener) } @Test fun `add order item`() { ctrl.addOrderItem(questA, questB) - verify(questTypeOrderDao).put(0, questA.name to questB.name) - verify(listener).onQuestTypeOrderAdded(questA, questB) + verifyInvokedExactlyOnce { questTypeOrderDao.put(0, questA.name to questB.name) } + verifyInvokedExactlyOnce { listener.onQuestTypeOrderAdded(questA, questB) } } @Test fun `add order item on not selected preset`() { ctrl.addOrderItem(questA, questB, 1) - verify(questTypeOrderDao).put(1, questA.name to questB.name) - verifyNoInteractions(listener) + verifyInvokedExactlyOnce { questTypeOrderDao.put(1, questA.name to questB.name) } + // verifyNoInteractions(listener) } @Test fun clear() { ctrl.clear() - verify(questTypeOrderDao).clear(0) - verify(listener).onQuestTypeOrdersChanged() + verifyInvokedExactlyOnce { questTypeOrderDao.clear(0) } + verifyInvokedExactlyOnce { listener.onQuestTypeOrdersChanged() } } @Test fun `clear not selected preset`() { ctrl.clear(1) - verify(questTypeOrderDao).clear(1) - verifyNoInteractions(listener) + verifyInvokedExactlyOnce { questTypeOrderDao.clear(1) } + // verifyNoInteractions(listener) } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeControllerTest.kt index 5adf558282..1e0f107236 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeControllerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/visiblequests/VisibleQuestTypeControllerTest.kt @@ -4,13 +4,15 @@ import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry import de.westnordost.streetcomplete.data.quest.TestQuestTypeA import de.westnordost.streetcomplete.data.quest.TestQuestTypeB import de.westnordost.streetcomplete.data.quest.TestQuestTypeDisabled -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.eq -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoInteractions +import de.westnordost.streetcomplete.testutils.verifyInvokedExactly +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.every +import io.mockative.mock + import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -19,11 +21,11 @@ import kotlin.test.assertTrue class VisibleQuestTypeControllerTest { - private lateinit var visibleQuestTypeDao: VisibleQuestTypeDao - private lateinit var questPresetsSource: QuestPresetsSource - private lateinit var questTypeRegistry: QuestTypeRegistry + @Mock private lateinit var visibleQuestTypeDao: VisibleQuestTypeDao + @Mock private lateinit var questPresetsSource: QuestPresetsSource + @Mock private lateinit var questTypeRegistry: QuestTypeRegistry private lateinit var ctrl: VisibleQuestTypeController - private lateinit var listener: VisibleQuestTypeSource.Listener + @Mock private lateinit var listener: VisibleQuestTypeSource.Listener private lateinit var questPresetsListener: QuestPresetsSource.Listener @@ -32,36 +34,36 @@ class VisibleQuestTypeControllerTest { private val quest2 = TestQuestTypeB() @BeforeTest fun setUp() { - visibleQuestTypeDao = mock() - questPresetsSource = mock() + visibleQuestTypeDao = mock(classOf()) + questPresetsSource = mock(classOf()) questTypeRegistry = QuestTypeRegistry(listOf( 0 to quest1, 1 to quest2, 2 to disabledQuest )) - on(questPresetsSource.addListener(any())).then { invocation -> - questPresetsListener = (invocation.arguments[0] as QuestPresetsSource.Listener) + every { questPresetsSource.addListener(any()) }.invokes { args -> + questPresetsListener = (args[0] as QuestPresetsSource.Listener) Unit } - on(questPresetsSource.selectedId).thenReturn(0) + every { questPresetsSource.selectedId }.returns(0) ctrl = VisibleQuestTypeController(visibleQuestTypeDao, questPresetsSource, questTypeRegistry) - listener = mock() + listener = mock(classOf()) ctrl.addListener(listener) } @Test fun `default visibility`() { - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf()) + every { visibleQuestTypeDao.getAll(0) }.returns(mutableMapOf()) assertTrue(ctrl.isVisible(quest1)) assertFalse(ctrl.isVisible(disabledQuest)) assertEquals(setOf(quest1, quest2), ctrl.getVisible()) } @Test fun `get visibility`() { - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf( + every { visibleQuestTypeDao.getAll(0) }.returns(mutableMapOf( quest1.name to false, disabledQuest.name to true )) @@ -71,62 +73,62 @@ class VisibleQuestTypeControllerTest { } @Test fun `visibility is cached`() { - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf( + every {visibleQuestTypeDao.getAll(0)}.returns(mutableMapOf( quest1.name to false )) ctrl.isVisible(quest1) ctrl.isVisible(quest1) ctrl.isVisible(quest1) - verify(visibleQuestTypeDao, times(1)).getAll(0) + verifyInvokedExactlyOnce { visibleQuestTypeDao.getAll(0) } } @Test fun `set visibility`() { - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf( + every { visibleQuestTypeDao.getAll(0)}.returns(mutableMapOf( quest1.name to false )) ctrl.setVisibility(quest1, true) assertTrue(ctrl.isVisible(quest1)) - verify(visibleQuestTypeDao).put(0, quest1.name, true) - verify(listener).onQuestTypeVisibilityChanged(quest1, true) + verifyInvokedExactlyOnce { visibleQuestTypeDao.put(0, quest1.name, true) } + verifyInvokedExactlyOnce { listener.onQuestTypeVisibilityChanged(quest1, true) } } @Test fun `set visibility in non-selected preset`() { - on(visibleQuestTypeDao.getAll(1)).thenReturn(mutableMapOf( + every {visibleQuestTypeDao.getAll(1) }.returns(mutableMapOf( quest1.name to false )) ctrl.setVisibility(quest1, true, 1) - verify(visibleQuestTypeDao).put(1, quest1.name, true) - verifyNoInteractions(listener) + verifyInvokedExactlyOnce { visibleQuestTypeDao.put(1, quest1.name, true) } + // TODO verifyNoInteractions(listener) } @Test fun `set visibility of several`() { - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf( + every { visibleQuestTypeDao.getAll(0) }.returns(mutableMapOf( quest1.name to true )) ctrl.setVisibilities(mapOf(quest1 to false, quest2 to false)) assertFalse(ctrl.isVisible(quest1)) assertFalse(ctrl.isVisible(quest2)) - verify(visibleQuestTypeDao).putAll( + verifyInvokedExactlyOnce { visibleQuestTypeDao.putAll( eq(0), eq(mapOf(quest1.name to false, quest2.name to false)) - ) - verify(listener).onQuestTypeVisibilitiesChanged() + )} + verifyInvokedExactlyOnce { listener.onQuestTypeVisibilitiesChanged() } } @Test fun `set visibility of several in non-selected preset`() { - on(visibleQuestTypeDao.getAll(1)).thenReturn(mutableMapOf( + every { visibleQuestTypeDao.getAll(1) }.returns(mutableMapOf( quest1.name to true )) ctrl.setVisibilities(mapOf(quest1 to false, quest2 to false), 1) - verify(visibleQuestTypeDao).putAll( + verifyInvokedExactlyOnce { visibleQuestTypeDao.putAll( eq(1), eq(mapOf(quest1.name to false, quest2.name to false)) - ) - verifyNoInteractions(listener) + )} + // TODO verifyNoInteractions(listener) } @Test fun `clear visibilities`() { - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf( + every { visibleQuestTypeDao.getAll(0) }.returns(mutableMapOf( quest1.name to false, disabledQuest.name to true )) @@ -134,26 +136,26 @@ class VisibleQuestTypeControllerTest { assertTrue(ctrl.isVisible(quest1)) assertFalse(ctrl.isVisible(disabledQuest)) assertEquals(setOf(quest1, quest2), ctrl.getVisible()) - verify(visibleQuestTypeDao).clear(0) - verify(listener).onQuestTypeVisibilitiesChanged() + verifyInvokedExactlyOnce { visibleQuestTypeDao.clear(0) } + verifyInvokedExactlyOnce { listener.onQuestTypeVisibilitiesChanged() } } @Test fun `clears visibilities of deleted quest preset`() { questPresetsListener.onDeletedQuestPreset(1) - verify(visibleQuestTypeDao).clear(1) - verifyNoInteractions(listener) + verifyInvokedExactlyOnce { visibleQuestTypeDao.clear(1) } + // TODO verifyNoInteractions(listener) } @Test fun `clears cache and notifies listener when changing quest preset`() { // make sure that visibilities are queried once from DB - on(visibleQuestTypeDao.getAll(0)).thenReturn(mutableMapOf()) + every { visibleQuestTypeDao.getAll(0) }.returns(mutableMapOf()) assertTrue(ctrl.isVisible(quest1)) questPresetsListener.onSelectedQuestPresetChanged() - verify(listener).onQuestTypeVisibilitiesChanged() + verifyInvokedExactlyOnce { listener.onQuestTypeVisibilitiesChanged() } // now they should be queried again: we expect getAll to be called twice assertTrue(ctrl.isVisible(quest1)) - verify(visibleQuestTypeDao, times(2)).getAll(0) + verifyInvokedExactly(2) { visibleQuestTypeDao.getAll(0) } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/MaxspeedKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/MaxspeedKtTest.kt index 974cf5f75e..1affb66a9c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/osm/MaxspeedKtTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/MaxspeedKtTest.kt @@ -3,17 +3,22 @@ package de.westnordost.streetcomplete.osm import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.CountryInfo import de.westnordost.streetcomplete.data.meta.CountryInfos +import de.westnordost.streetcomplete.data.meta.IncompleteCountryInfo import de.westnordost.streetcomplete.data.meta.SpeedMeasurementUnit -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class MaxspeedKtTest { + @Mock val countryInfos: CountryInfos = mock(classOf()) + @Test fun `get maxspeed`() { assertEquals(null, getMaxspeedInKmh(mapOf())) assertEquals(null, getMaxspeedInKmh(mapOf("unrelated" to "string"))) @@ -40,10 +45,10 @@ class MaxspeedKtTest { } @Test fun `guess maxspeed mph zone`() { - val countryInfos: CountryInfos = mock() - val countryInfo: CountryInfo = mock() - on(countryInfo.speedUnits).thenReturn(listOf(SpeedMeasurementUnit.MILES_PER_HOUR)) - on(countryInfos.get(any())).thenReturn(countryInfo) + val countryInfos: CountryInfos = mock(classOf()) + val countryInfo = CountryInfo(listOf(IncompleteCountryInfo("a", speedUnits = listOf(SpeedMeasurementUnit.MILES_PER_HOUR)))) + + every { countryInfos.get(any()) }.returns(countryInfo) assertEquals(32.18688f, guessMaxspeedInKmh(mapOf("maxspeed:type" to "DE:zone:20"), countryInfos)!!, 0.1f) assertEquals(48.28032f, guessMaxspeedInKmh(mapOf("maxspeed:type" to "DE:zone30"), countryInfos)!!, 0.1f) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/cycleway/AddCyclewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/cycleway/AddCyclewayTest.kt index 411b13bbd2..e54f25f2ad 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/cycleway/AddCyclewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/cycleway/AddCyclewayTest.kt @@ -1,9 +1,8 @@ package de.westnordost.streetcomplete.quests.cycleway import de.westnordost.streetcomplete.data.meta.CountryInfo +import de.westnordost.streetcomplete.data.meta.IncompleteCountryInfo import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.pGeom import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds @@ -20,7 +19,7 @@ class AddCyclewayTest { private lateinit var questType: AddCycleway @BeforeTest fun setUp() { - countryInfo = mock() + countryInfo = CountryInfo(emptyList()) questType = AddCycleway { _ -> countryInfo } } @@ -143,8 +142,8 @@ class AddCyclewayTest { )) val mapData = TestMapDataWithGeometry(listOf(way)) mapData.wayGeometriesById[1L] = pGeom(0.0, 0.0) - on(countryInfo.countryCode).thenReturn("DE") - on(countryInfo.hasAdvisoryCycleLane).thenReturn(true) + countryInfo = CountryInfo(listOf(IncompleteCountryInfo("DE", hasAdvisoryCycleLane = true))) + questType = AddCycleway { _ -> countryInfo } assertEquals(1, questType.getApplicableElements(mapData).toList().size) // because we don't know if we are in Belgium @@ -158,8 +157,8 @@ class AddCyclewayTest { )) val mapData = TestMapDataWithGeometry(listOf(way)) mapData.wayGeometriesById[1L] = pGeom(0.0, 0.0) - on(countryInfo.countryCode).thenReturn("BE") - on(countryInfo.hasAdvisoryCycleLane).thenReturn(true) + countryInfo = CountryInfo(listOf(IncompleteCountryInfo("BE", hasAdvisoryCycleLane = true))) + questType = AddCycleway { _ -> countryInfo } assertEquals(0, questType.getApplicableElements(mapData).toList().size) // because we don't know if we are in Belgium diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt index 9481c073d9..2a2386d8e4 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/existence/CheckExistenceTest.kt @@ -1,15 +1,22 @@ package de.westnordost.streetcomplete.quests.existence -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.osmfeatures.Feature import de.westnordost.streetcomplete.testutils.node import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue class CheckExistenceTest { + // dummy + @Mock + private lateinit var feature: Feature + private val questType = CheckExistence { element -> - if (element.tags["amenity"] == "telephone") mock() else null + if (element.tags["amenity"] == "telephone") mock(classOf()) else null } @Test fun `isApplicableTo returns false for known places with recently edited amenity=telephone`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/max_height/AddMaxPhysicalHeightTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/max_height/AddMaxPhysicalHeightTest.kt index a8e4fa370d..dab2be240e 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/max_height/AddMaxPhysicalHeightTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/max_height/AddMaxPhysicalHeightTest.kt @@ -1,16 +1,20 @@ package de.westnordost.streetcomplete.quests.max_height -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.screens.measure.ArSupportChecker import de.westnordost.streetcomplete.testutils.node import de.westnordost.streetcomplete.testutils.way +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail class AddMaxPhysicalHeightTest { - - private val questType = AddMaxPhysicalHeight(mock()) + @Mock + private val arSupportChecker: ArSupportChecker = mock(classOf()) + private val questType = AddMaxPhysicalHeight(arSupportChecker) @Test fun `applicable if maxheight is below default`() { assertTrue(isApplicableTo(mapOf("maxheight" to "below_default"))) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt index fad735f172..f973213493 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt @@ -6,24 +6,28 @@ import de.westnordost.osm_opening_hours.model.Range import de.westnordost.osm_opening_hours.model.Rule import de.westnordost.osm_opening_hours.model.TimeSpan import de.westnordost.osm_opening_hours.model.Weekday +import de.westnordost.osmfeatures.Feature import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryModify import de.westnordost.streetcomplete.osm.nowAsCheckDateString import de.westnordost.streetcomplete.osm.toCheckDate import de.westnordost.streetcomplete.quests.answerApplied import de.westnordost.streetcomplete.quests.answerAppliedTo -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.ktx.toEpochMilli +import io.mockative.Mock +import io.mockative.classOf import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class AddOpeningHoursTest { + @Mock + private val feature: Feature = io.mockative.mock(classOf()) - private val questType = AddOpeningHours(mock()) + private val questType = AddOpeningHours { _ -> feature } @Test fun `apply description answer`() { assertEquals( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSignedTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSignedTest.kt index 732e056150..412286b512 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSignedTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours_signed/CheckOpeningHoursSignedTest.kt @@ -1,19 +1,24 @@ package de.westnordost.streetcomplete.quests.opening_hours_signed +import de.westnordost.osmfeatures.Feature import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryDelete import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryModify import de.westnordost.streetcomplete.osm.nowAsCheckDateString import de.westnordost.streetcomplete.quests.answerAppliedTo -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node +import io.mockative.Mock +import io.mockative.classOf import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class CheckOpeningHoursSignedTest { - private val questType = CheckOpeningHoursSigned(mock()) + @Mock + private val feature: Feature = io.mockative.mock(classOf()) + + private val questType = CheckOpeningHoursSigned { _ -> feature } @Test fun `is applicable to old place`() { assertTrue(questType.isApplicableTo(node(timestamp = 0, tags = mapOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceNameTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceNameTest.kt index 4480449ad9..aacdeb000e 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceNameTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceNameTest.kt @@ -1,15 +1,21 @@ package de.westnordost.streetcomplete.quests.place_name +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmfeatures.Feature import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryAdd import de.westnordost.streetcomplete.osm.LocalizedName import de.westnordost.streetcomplete.quests.answerApplied -import de.westnordost.streetcomplete.testutils.mock +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertEquals class AddPlaceNameTest { + @Mock + private val feature: Feature = mock(classOf()) - private val questType = AddPlaceName(mock()) + private val questType = AddPlaceName { _ -> feature } @Test fun `apply no name answer`() { assertEquals( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShapeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShapeTest.kt index 9b4f9b3b28..abcae21253 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShapeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShapeTest.kt @@ -1,9 +1,8 @@ package de.westnordost.streetcomplete.quests.roof_shape import de.westnordost.streetcomplete.data.meta.CountryInfo +import de.westnordost.streetcomplete.data.meta.IncompleteCountryInfo import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry -import de.westnordost.streetcomplete.testutils.mock -import de.westnordost.streetcomplete.testutils.on import de.westnordost.streetcomplete.testutils.pGeom import de.westnordost.streetcomplete.testutils.way import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder @@ -17,7 +16,7 @@ class AddRoofShapeTest { private lateinit var questType: AddRoofShape @BeforeTest fun setUp() { - countryInfo = mock() + countryInfo = CountryInfo(emptyList()) questType = AddRoofShape { _ -> countryInfo } } @@ -90,7 +89,8 @@ class AddRoofShapeTest { } @Test fun `create quest for 0 or null-level roofs only in countries with no flat roofs`() { - on(countryInfo.roofsAreUsuallyFlat).thenReturn(false) + val countryInfo = CountryInfo(listOf(IncompleteCountryInfo("DE", roofsAreUsuallyFlat = false ))) + val questType = AddRoofShape { _ -> countryInfo } val element = way(1, tags = mapOf("roof:levels" to "0", "building" to "apartments")) val element2 = way(2, tags = mapOf("building:levels" to "3", "building" to "apartments")) @@ -105,7 +105,8 @@ class AddRoofShapeTest { } @Test fun `create quest for 0 or null-level roofs not in countries with flat roofs`() { - on(countryInfo.roofsAreUsuallyFlat).thenReturn(true) + val countryInfo = CountryInfo(listOf(IncompleteCountryInfo("DE", roofsAreUsuallyFlat = true ))) + val questType = AddRoofShape { _ -> countryInfo } val element = way(1, tags = mapOf("roof:levels" to "0", "building" to "apartments")) val element2 = way(1, tags = mapOf("building:levels" to "3", "building" to "apartments")) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt index 467ef8a239..71569a9e2b 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/shop_type/CheckShopExistenceTest.kt @@ -1,14 +1,20 @@ package de.westnordost.streetcomplete.quests.shop_type +import de.westnordost.osmfeatures.Feature import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry -import de.westnordost.streetcomplete.testutils.mock import de.westnordost.streetcomplete.testutils.node +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertEquals class CheckShopExistenceTest { + // dummy + @Mock private lateinit var feature: Feature + private val questType = CheckShopExistence { element -> - if (element.tags["shop"] == "greengrocer") mock() else null + if (element.tags["shop"] == "greengrocer") mock(classOf()) else null } @Test diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/width/AddRoadWidthTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/width/AddRoadWidthTest.kt index 6e493835ce..bfeedb30c6 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/width/AddRoadWidthTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/width/AddRoadWidthTest.kt @@ -6,15 +6,19 @@ import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryMo import de.westnordost.streetcomplete.osm.LengthInMeters import de.westnordost.streetcomplete.quests.answerApplied import de.westnordost.streetcomplete.quests.answerAppliedTo -import de.westnordost.streetcomplete.testutils.mock +import de.westnordost.streetcomplete.screens.measure.ArSupportChecker import de.westnordost.streetcomplete.testutils.way +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class AddRoadWidthTest { - private val quest = AddRoadWidth(mock()) + @Mock private val arCheck: ArSupportChecker = mock(classOf()) + private val quest = AddRoadWidth(arCheck) @Test fun `is applicable to residential roads if speed below 33`() { assertTrue(quest.isApplicableTo(way(tags = mapOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/testutils/Mockative.kt b/app/src/test/java/de/westnordost/streetcomplete/testutils/Mockative.kt new file mode 100644 index 0000000000..e5ba104270 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/testutils/Mockative.kt @@ -0,0 +1,17 @@ +package de.westnordost.streetcomplete.testutils + +import io.mockative.ResultBuilder +import io.mockative.classOf +import io.mockative.coVerify +import io.mockative.every +import io.mockative.mock +import io.mockative.verify + +fun verifyInvokedExactlyOnce(block: () -> R): Unit = verify(block).wasInvoked(exactly = 1) +suspend fun coVerifyInvokedExactlyOnce(block: suspend () -> R): Unit = coVerify(block).wasInvoked(exactly = 1) +fun verifyInvokedExactly(times : Int, block: () -> R): Unit = verify(block).wasInvoked(exactly = times) + +fun on(methodCall: () -> T): ResultBuilder = every (methodCall) + +// doesn't work. why? +// inline fun mockA(): T = mock(classOf()) diff --git a/app/src/test/java/de/westnordost/streetcomplete/testutils/Mockito.kt b/app/src/test/java/de/westnordost/streetcomplete/testutils/Mockito.kt deleted file mode 100644 index 6a18f05823..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/testutils/Mockito.kt +++ /dev/null @@ -1,19 +0,0 @@ -package de.westnordost.streetcomplete.testutils - -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatcher -import org.mockito.Mockito -import org.mockito.stubbing.OngoingStubbing -import org.mockito.stubbing.Stubber - -fun eq(obj: T): T = Mockito.eq(obj) -fun argThat(matcher: (T) -> Boolean): T = Mockito.argThat(ArgumentMatcher(matcher)) -fun any(): T = Mockito.any() -fun capture(argumentCaptor: ArgumentCaptor): T = argumentCaptor.capture() -inline fun argumentCaptor(): ArgumentCaptor = - ArgumentCaptor.forClass(T::class.java) - -fun on(methodCall: T): OngoingStubbing = Mockito.`when`(methodCall) -fun Stubber.on(mock: T): T = this.`when`(mock) - -inline fun mock(): T = Mockito.mock(T::class.java) diff --git a/app/src/test/java/de/westnordost/streetcomplete/testutils/TestDataShortcuts.kt b/app/src/test/java/de/westnordost/streetcomplete/testutils/TestDataShortcuts.kt index ac596aa8cc..4e557ec357 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/testutils/TestDataShortcuts.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/testutils/TestDataShortcuts.kt @@ -2,6 +2,7 @@ package de.westnordost.streetcomplete.testutils import de.westnordost.streetcomplete.data.osm.edits.ElementEdit import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction +import de.westnordost.streetcomplete.data.osm.edits.ElementIdProvider import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapChanges import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.edits.update_tags.UpdateElementTagsAction @@ -157,3 +158,5 @@ fun osmQuestKey( ) = OsmQuestKey(elementType, elementId, questTypeName) val QUEST_TYPE = TestQuestTypeA() + +fun elementIdProvider() = ElementIdProvider(emptyList()) diff --git a/app/src/test/java/de/westnordost/streetcomplete/util/SpatialCacheTest.kt b/app/src/test/java/de/westnordost/streetcomplete/util/SpatialCacheTest.kt index ce9c9ef151..3525e73b2a 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/util/SpatialCacheTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/util/SpatialCacheTest.kt @@ -8,15 +8,19 @@ import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.data.osm.mapdata.Node import de.westnordost.streetcomplete.data.osm.mapdata.NodeDao -import de.westnordost.streetcomplete.testutils.any -import de.westnordost.streetcomplete.testutils.mock + import de.westnordost.streetcomplete.testutils.node -import de.westnordost.streetcomplete.testutils.on +import de.westnordost.streetcomplete.testutils.verifyInvokedExactlyOnce + import de.westnordost.streetcomplete.util.ktx.containsExactlyInAnyOrder import de.westnordost.streetcomplete.util.math.enclosingBoundingBox import de.westnordost.streetcomplete.util.math.intersect import de.westnordost.streetcomplete.util.math.isCompletelyInside -import org.mockito.Mockito.verify +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.every +import io.mockative.mock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -25,6 +29,8 @@ import kotlin.test.assertTrue internal class SpatialCacheTest { + @Mock private val nodeDB: NodeDao = mock(classOf()) + @Test fun `update doesn't put if tile doesn't exist`() { val node = node(1) val cache = SpatialCache( @@ -96,13 +102,13 @@ internal class SpatialCacheTest { val node = node(1, LatLon(1.0, 1.0)) val nodeTile = node.position.enclosingTilePos(16) val nodeBBox = nodeTile.asBoundingBox(16) - val nodeDB: NodeDao = mock() - on(nodeDB.getAll(nodeTile.asBoundingBox(16))).thenReturn(listOf(node)) + val nodeDB: NodeDao = mock(classOf()) + every { nodeDB.getAll(nodeTile.asBoundingBox(16)) }.returns(listOf(node)) val cache = SpatialCache( 16, 4, null, { nodeDB.getAll(it) }, Node::id, Node::position ) assertEquals(listOf(node), cache.get(LatLon(1.0, 1.0).enclosingBoundingBox(0.0))) - verify(nodeDB).getAll(nodeBBox) + verifyInvokedExactlyOnce { nodeDB.getAll(nodeBBox) } } @Test fun `get returns all the data in the bbox`() { @@ -145,15 +151,15 @@ internal class SpatialCacheTest { assertEquals(2, topHalf.size) // subList with exclusive indexTo is a bit counter-intuitive assertEquals(2, bottomHalf.size) - val nodeDB: NodeDao = mock() - on(nodeDB.getAll(any())).thenReturn(listOf()) + val nodeDB: NodeDao = mock(classOf()) + every { nodeDB.getAll(any()) }.returns(listOf()) val cache = SpatialCache( 16, 4, null, { nodeDB.getAll(it) }, Node::id, Node::position ) cache.get(topHalf.minTileRect()!!.asBoundingBox(16)) - verify(nodeDB).getAll(topHalf.minTileRect()!!.asBoundingBox(16)) + verifyInvokedExactlyOnce { nodeDB.getAll(topHalf.minTileRect()!!.asBoundingBox(16)) } cache.get(fullBBox) - verify(nodeDB).getAll(bottomHalf.minTileRect()!!.asBoundingBox(16)) + verifyInvokedExactlyOnce { nodeDB.getAll(bottomHalf.minTileRect()!!.asBoundingBox(16)) } } @Test fun `update removes element if moving to non-cached tile`() {