refactor: Extract FakeMastodonApiModule (#152)

Extract `FakeMastodonApiModule` from `ComposeActivityTest` to make it
usable in other tests. Update `MainActivityTest` to use the extracted
code.
This commit is contained in:
Nik Clayton 2023-10-11 15:39:51 +02:00 committed by GitHub
parent 53e7842439
commit f88599908b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 69 deletions

View File

@ -31,22 +31,16 @@ import app.pachli.components.notifications.NotificationHelper
import app.pachli.db.AccountEntity
import app.pachli.db.AccountManager
import app.pachli.db.DraftsAlert
import app.pachli.di.MastodonApiModule
import app.pachli.entity.Account
import app.pachli.entity.Notification
import app.pachli.entity.TimelineAccount
import app.pachli.network.MastodonApi
import app.pachli.rules.lazyActivityScenarioRule
import at.connyduck.calladapter.networkresult.NetworkResult
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.CustomTestApplication
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import dagger.hilt.components.SingletonComponent
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
@ -56,11 +50,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
import org.mockito.kotlin.stub
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.util.concurrent.BackgroundExecutor.runInBackground
import org.robolectric.annotation.Config
import java.util.Date
import javax.inject.Singleton
import javax.inject.Inject
open class PachliHiltApplication : PachliApplication()
@ -70,7 +66,6 @@ interface HiltTestApplication
@HiltAndroidTest
@Config(application = HiltTestApplication_Application::class)
@RunWith(AndroidJUnit4::class)
@UninstallModules(MastodonApiModule::class)
class MainActivityTest {
@get:Rule(order = 0)
var hilt = HiltAndroidRule(this)
@ -89,28 +84,20 @@ class MainActivityTest {
isActive = true,
)
@InstallIn(SingletonComponent::class)
@Module
object FakeNetworkModule {
val account = Account(
id = "1",
localUsername = "",
username = "",
displayName = "",
createdAt = Date(),
note = "",
url = "",
avatar = "",
header = "",
)
val account = Account(
id = "1",
localUsername = "",
username = "",
displayName = "",
createdAt = Date(),
note = "",
url = "",
avatar = "",
header = "",
)
@Provides
@Singleton
fun providesApi(): MastodonApi = mock {
onBlocking { accountVerifyCredentials() } doReturn NetworkResult.success(account)
onBlocking { listAnnouncements(false) } doReturn NetworkResult.success(emptyList())
}
}
@Inject
lateinit var mastodonApi: MastodonApi
@BindValue
@JvmField
@ -122,6 +109,14 @@ class MainActivityTest {
@Before
fun setup() {
hilt.inject()
reset(mastodonApi)
mastodonApi.stub {
onBlocking { accountVerifyCredentials() } doReturn NetworkResult.success(account)
onBlocking { listAnnouncements(false) } doReturn NetworkResult.success(emptyList())
}
WorkManagerTestInitHelper.initializeTestWorkManager(
ApplicationProvider.getApplicationContext(),
)

View File

@ -24,7 +24,6 @@ import app.pachli.PachliApplication
import app.pachli.R
import app.pachli.components.instanceinfo.InstanceInfoRepository
import app.pachli.db.AccountManager
import app.pachli.di.MastodonApiModule
import app.pachli.entity.Account
import app.pachli.entity.Instance
import app.pachli.entity.InstanceConfiguration
@ -32,14 +31,9 @@ import app.pachli.entity.StatusConfiguration
import app.pachli.network.MastodonApi
import app.pachli.rules.lazyActivityScenarioRule
import at.connyduck.calladapter.networkresult.NetworkResult
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.testing.CustomTestApplication
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -48,15 +42,16 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
import org.mockito.kotlin.stub
import org.robolectric.annotation.Config
import org.robolectric.fakes.RoboMenuItem
import java.time.Instant
import java.util.Date
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
open class PachliHiltApplication : PachliApplication()
@ -66,7 +61,6 @@ interface HiltTestApplication
@HiltAndroidTest
@Config(application = HiltTestApplication_Application::class)
@RunWith(AndroidJUnit4::class)
@UninstallModules(MastodonApiModule::class)
class ComposeActivityTest {
@get:Rule(order = 0)
var hilt = HiltAndroidRule(this)
@ -76,28 +70,10 @@ class ComposeActivityTest {
launchActivity = false,
)
@InstallIn(SingletonComponent::class)
@Module
object FakeMastodonApiModule {
/**
* Callback invoked when the mock [MastodonApi.getInstance] is called. Set this
* in tests to adjust aspects of the fake server's configuration.
*/
var getInstanceCallback: (() -> Instance)? = null
private var getInstanceCallback: (() -> Instance)? = null
@Provides
@Singleton
fun providesApi(): MastodonApi = mock {
onBlocking { getCustomEmojis() } doReturn NetworkResult.success(emptyList())
onBlocking { getInstance() } doReturn getInstanceCallback?.invoke().let { instance ->
if (instance == null) {
NetworkResult.failure(Throwable())
} else {
NetworkResult.success(instance)
}
}
}
}
@Inject
lateinit var mastodonApi: MastodonApi
@Inject
lateinit var accountManager: AccountManager
@ -105,6 +81,22 @@ class ComposeActivityTest {
@Before
fun setup() {
hilt.inject()
getInstanceCallback = null
reset(mastodonApi)
mastodonApi.stub {
onBlocking { getCustomEmojis() } doReturn NetworkResult.success(emptyList())
onBlocking { getInstance() } doAnswer {
getInstanceCallback?.invoke().let { instance ->
if (instance == null) {
NetworkResult.failure(Throwable())
} else {
NetworkResult.success(instance)
}
}
}
}
accountManager.addAccount(
accessToken = "token",
domain = "domain.example",
@ -123,8 +115,6 @@ class ComposeActivityTest {
header = "",
),
)
FakeMastodonApiModule.getInstanceCallback = null
}
@Test
@ -187,7 +177,7 @@ class ComposeActivityTest {
@Test
fun whenMaximumTootCharsIsNull_defaultLimitIsUsed() {
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(null) }
getInstanceCallback = { getInstanceWithCustomConfiguration(null) }
rule.launch()
rule.getScenario().onActivity {
assertEquals(
@ -200,7 +190,7 @@ class ComposeActivityTest {
@Test
fun whenMaximumTootCharsIsPopulated_customLimitIsUsed() {
val customMaximum = 1000
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(customMaximum, getCustomInstanceConfiguration(maximumStatusCharacters = customMaximum)) }
getInstanceCallback = { getInstanceWithCustomConfiguration(customMaximum, getCustomInstanceConfiguration(maximumStatusCharacters = customMaximum)) }
rule.launch()
rule.getScenario().onActivity {
assertEquals(customMaximum, it.maximumTootCharacters)
@ -210,7 +200,7 @@ class ComposeActivityTest {
@Test
fun whenOnlyLegacyMaximumTootCharsIsPopulated_customLimitIsUsed() {
val customMaximum = 1000
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(customMaximum) }
getInstanceCallback = { getInstanceWithCustomConfiguration(customMaximum) }
rule.launch()
rule.getScenario().onActivity {
assertEquals(customMaximum, it.maximumTootCharacters)
@ -220,7 +210,7 @@ class ComposeActivityTest {
@Test
fun whenOnlyConfigurationMaximumTootCharsIsPopulated_customLimitIsUsed() {
val customMaximum = 1000
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(null, getCustomInstanceConfiguration(maximumStatusCharacters = customMaximum)) }
getInstanceCallback = { getInstanceWithCustomConfiguration(null, getCustomInstanceConfiguration(maximumStatusCharacters = customMaximum)) }
rule.launch()
rule.getScenario().onActivity {
assertEquals(customMaximum, it.maximumTootCharacters)
@ -230,7 +220,7 @@ class ComposeActivityTest {
@Test
fun whenDifferentCharLimitsArePopulated_statusConfigurationLimitIsUsed() {
val customMaximum = 1000
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(customMaximum, getCustomInstanceConfiguration(maximumStatusCharacters = customMaximum * 2)) }
getInstanceCallback = { getInstanceWithCustomConfiguration(customMaximum, getCustomInstanceConfiguration(maximumStatusCharacters = customMaximum * 2)) }
rule.launch()
rule.getScenario().onActivity {
assertEquals(customMaximum * 2, it.maximumTootCharacters)
@ -295,7 +285,7 @@ class ComposeActivityTest {
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
val additionalContent = "Check out this @image #search result: "
val customUrlLength = 16
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(configuration = getCustomInstanceConfiguration(charactersReservedPerUrl = customUrlLength)) }
getInstanceCallback = { getInstanceWithCustomConfiguration(configuration = getCustomInstanceConfiguration(charactersReservedPerUrl = customUrlLength)) }
rule.launch()
rule.getScenario().onActivity {
insertSomeTextInContent(it, additionalContent + url)
@ -313,7 +303,7 @@ class ComposeActivityTest {
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 18 // The intention is that this is longer than shortUrl.length
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(configuration = getCustomInstanceConfiguration(charactersReservedPerUrl = customUrlLength)) }
getInstanceCallback = { getInstanceWithCustomConfiguration(configuration = getCustomInstanceConfiguration(charactersReservedPerUrl = customUrlLength)) }
rule.launch()
rule.getScenario().onActivity {
insertSomeTextInContent(it, shortUrl + additionalContent + url)
@ -329,7 +319,7 @@ class ComposeActivityTest {
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 16
FakeMastodonApiModule.getInstanceCallback = { getInstanceWithCustomConfiguration(configuration = getCustomInstanceConfiguration(charactersReservedPerUrl = customUrlLength)) }
getInstanceCallback = { getInstanceWithCustomConfiguration(configuration = getCustomInstanceConfiguration(charactersReservedPerUrl = customUrlLength)) }
rule.launch()
rule.getScenario().onActivity {
insertSomeTextInContent(it, url + additionalContent + url)

View File

@ -0,0 +1,60 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.di
import app.pachli.network.MastodonApi
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import org.mockito.kotlin.mock
import javax.inject.Singleton
/**
* Provides an empty mock. Use like:
*
* ```kotlin
* @Inject
* lateinit var mastodonApi: MastodonApi
*
* // ...
*
* @Before
* fun setup() {
* hilt.inject()
*
* reset(mastodonApi)
* mastodonApi.stub {
* onBlocking { someFunction() } doReturn SomeValue
* // ...
* }
*
* // ...
* }
* ```
*/
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [MastodonApiModule::class],
)
@Module
object FakeMastodonApiModule {
@Provides
@Singleton
fun providesApi(): MastodonApi = mock()
}