Test/2819 add campaigns api tests (#6529)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run

* test:add mock JSON resource files for campaigns API responses

* feat:make campaign model fields mutable to allow for correct deserialization

* test:implement unit tests for fetching campaigns and fix DTO mocking logic

* test:implement unit tests for fetching campaigns and fix DTO mocking logic

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
VoidRaven 2025-10-19 19:28:14 +05:30 committed by GitHub
parent 3a55583460
commit def33552f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 182 additions and 28 deletions

View file

@ -7,8 +7,8 @@ import com.google.gson.annotations.SerializedName
*/
class CampaignConfig {
@SerializedName("showOnlyLiveCampaigns")
private val showOnlyLiveCampaigns = false
var showOnlyLiveCampaigns = false
@SerializedName("sortBy")
private val sortBy: String? = null
}
var sortBy: String? = null
}

View file

@ -8,8 +8,8 @@ import fr.free.nrw.commons.campaigns.models.Campaign
*/
class CampaignResponseDTO {
@SerializedName("config")
val campaignConfig: CampaignConfig? = null
var campaignConfig: CampaignConfig? = null
@SerializedName("campaigns")
val campaigns: List<Campaign>? = null
}
var campaigns: List<Campaign>? = null
}

View file

@ -3,6 +3,10 @@ package fr.free.nrw.commons
import com.google.gson.Gson
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.times
import fr.free.nrw.commons.campaigns.CampaignResponseDTO
import fr.free.nrw.commons.campaigns.CampaignConfig
import fr.free.nrw.commons.campaigns.models.Campaign
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
@ -10,13 +14,22 @@ import fr.free.nrw.commons.nearby.model.NearbyQueryParams
import okhttp3.Call
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.mockwebserver.MockResponse
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.times
import org.mockito.Mockito.eq
import org.mockito.MockitoAnnotations
import java.io.BufferedReader
import java.io.InputStreamReader
import java.lang.Exception
class OkHttpJsonApiClientTests {
@ -45,34 +58,43 @@ class OkHttpJsonApiClientTests {
@Mock
lateinit var response: Response
@Mock
lateinit var responseBody: ResponseBody
private lateinit var mockWebServer: TestWebServer
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
okHttpJsonApiClient =
OkHttpJsonApiClient(
okhttpClient,
depictsClient,
wikiMediaToolforgeUrl,
sparqlQueryUrl,
campaignsUrl,
gson,
)
mockWebServer = TestWebServer()
mockWebServer.setUp()
okHttpJsonApiClient = OkHttpJsonApiClient(
okhttpClient,
depictsClient,
wikiMediaToolforgeUrl,
sparqlQueryUrl,
mockWebServer.getUrl(), //use the mock server for the campaignsUrl
gson
)
Mockito.`when`(okhttpClient.newCall(any())).thenReturn(call)
Mockito.`when`(call.execute()).thenReturn(response)
Mockito.`when`(response.isSuccessful).thenReturn(false)
Mockito.`when`(response.message).thenReturn("test")
Mockito.`when`(response.body).thenReturn(responseBody)
Mockito.`when`(responseBody.string()).thenReturn("{\"error\": \"test\"}")
}
@Test
fun testGetNearbyPlacesCustomQuery() {
Mockito.`when`(response.message).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, "test")
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyPlaces(NearbyQueryParams.Rectangular(latLng, latLng), "test", true, "test")
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
verify(okhttpClient, times(2)).newCall(any())
verify(call, times(2)).execute()
@ -80,11 +102,10 @@ class OkHttpJsonApiClientTests {
@Test
fun testGetNearbyPlaces() {
Mockito.`when`(response.message).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, null)
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyPlaces(
@ -93,9 +114,8 @@ class OkHttpJsonApiClientTests {
true,
null
)
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyPlaces(
@ -105,7 +125,7 @@ class OkHttpJsonApiClientTests {
null
)
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
verify(okhttpClient, times(3)).newCall(any())
verify(call, times(3)).execute()
@ -113,18 +133,121 @@ class OkHttpJsonApiClientTests {
@Test
fun testGetNearbyItemCount() {
Mockito.`when`(response.message).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyItemCount(NearbyQueryParams.Radial(latLng, 10f))
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyItemCount(NearbyQueryParams.Rectangular(latLng, latLng))
} catch (e: Exception) {
assert(e.message.equals("test"))
assertEquals("test", e.message)
}
verify(okhttpClient, times(2)).newCall(any())
verify(call, times(2)).execute()
}
}
@Test
fun testGetCampaignsWithData() {
//loads the json response from resources
val jsonResponse = loadJsonFromResource("campaigns_response_with_data.json")
//mocks the succesfull response chain
Mockito.`when`(response.isSuccessful).thenReturn(true)
Mockito.`when`(response.message).thenReturn("OK")
Mockito.`when`(response.body).thenReturn(responseBody)
Mockito.`when`(responseBody.string()).thenReturn(jsonResponse)
val campaignResponse = CampaignResponseDTO().apply {
campaignConfig = CampaignConfig().apply {
showOnlyLiveCampaigns = false
sortBy = "startDate"
}
campaigns = listOf(
Campaign().apply {
title = "Wiki Loves Monuments"
isWLMCampaign = true
},
Campaign().apply {
title = "Wiki Loves Nature"
isWLMCampaign = false
}
)
}
//any() for the string argument and eq() for the class argument.
Mockito.`when`(
gson.fromJson(
any<String>(),
eq(CampaignResponseDTO::class.java)
)
).thenReturn(campaignResponse)
//call the getCampaigns
val result: CampaignResponseDTO? = okHttpJsonApiClient.getCampaigns().blockingGet()
//verify the results
assertNotNull(result)
assertNotNull(result?.campaigns)
assertEquals(2, result?.campaigns!!.size)
assertEquals("Wiki Loves Monuments", result.campaigns!![0].title)
assertTrue(result.campaigns!![0].isWLMCampaign)
assertEquals("Wiki Loves Nature", result.campaigns!![1].title)
assertEquals(false, result.campaigns!![1].isWLMCampaign)
assertNotNull(result.campaignConfig)
assertFalse(result.campaignConfig!!.showOnlyLiveCampaigns)
assertEquals("startDate", result.campaignConfig!!.sortBy)
}
@Test
fun testGetCampaignsEmpty() {
//loads the empty json response
val jsonResponse = loadJsonFromResource("campaigns_response_empty.json")
//mocks the successful response chain
Mockito.`when`(response.isSuccessful).thenReturn(true)
Mockito.`when`(response.message).thenReturn("OK")
Mockito.`when`(response.body).thenReturn(responseBody)
Mockito.`when`(responseBody.string()).thenReturn(jsonResponse)
val campaignResponse = CampaignResponseDTO().apply {
campaignConfig = CampaignConfig().apply {
showOnlyLiveCampaigns = false
sortBy = "startDate"
}
campaigns = emptyList()
}
//use any() for the string argument and eq() for the class argument.
Mockito.`when`(
gson.fromJson(
any<String>(),
eq(CampaignResponseDTO::class.java)
)
).thenReturn(campaignResponse)
//calls getCampaigns
val result: CampaignResponseDTO? = okHttpJsonApiClient.getCampaigns().blockingGet()
//verify the results
assertNotNull(result)
assertNotNull(result?.campaigns)
assertTrue(result?.campaigns!!.isEmpty())
assertNotNull(result.campaignConfig)
assertFalse(result.campaignConfig!!.showOnlyLiveCampaigns)
assertEquals("startDate", result.campaignConfig!!.sortBy)
}
fun loadJsonFromResource(fileName: String): String {
val resourcePath = "raw/$fileName"
//uses the classloader to find the resource in the test environment
val inputStream = javaClass.classLoader?.getResourceAsStream(resourcePath)
if (inputStream != null) {
//reads the entire stream content
return BufferedReader(InputStreamReader(inputStream)).use { it.readText() }
}
//throws an exception with the correct expected path
throw IllegalArgumentException("Resource $fileName not found. Please ensure the file is located in app/src/test/resources/raw/")
}
}

View file

@ -0,0 +1,7 @@
{
"config": {
"showOnlyLiveCampaigns": false,
"sortBy": "startDate"
},
"campaigns": []
}

View file

@ -0,0 +1,24 @@
{
"config": {
"showOnlyLiveCampaigns": false,
"sortBy": "startDate"
},
"campaigns": [
{
"title": "Wiki Loves Monuments",
"description": "A campaign to photograph monuments",
"startDate": "2025-09-01",
"endDate": "2025-09-30",
"link": "https://commons.wikimedia.org/wiki/Campaign:Wiki_Loves_Monuments",
"isWLMCampaign": true
},
{
"title": "Wiki Loves Nature",
"description": "A campaign to photograph nature",
"startDate": "2025-06-01",
"endDate": "2025-06-30",
"link": "https://commons.wikimedia.org/wiki/Campaign:Wiki_Loves_Nature",
"isWLMCampaign": false
}
]
}