Mock Lazy constructor parameter - android

it is my first question so apologies if I did something wrong.
I'm testing a usecase that takes an injected Lazy Dagger constructor parameter.
import dagger.Lazy
class TrackSpeed (val analyticsRepository: Lazy<AnalyticsRepository>) {
fun execute(timeMillis: Long) {
analyticsRepository.get().trackSpeed(timeMillis)
}
}
I don't know how to mock AnalyticsRepository as it is Lazy.
This is how my test class looks right now:
class TrackSpeedTest {
private lateinit var trackSpeed: TrackSpeed
private val analyticsRepository: Lazy<AnalyticsRepository> = mock()
#Before
fun setUp() {
trackSpeed = TrackSpeed(analyticsRepository)
}
#Test
fun testTrackSpeed() {
val time: Long = 0
trackSpeed.execute(time)
verify(analyticsRepository.get(), times(1))
.trackSpeed(time)
}
There are no compilation errors but when I run the test it fails with this exception:
java.lang.ClassCastException: io.reactivex.internal.operators.flowable.FlowableError cannot be cast to com.package.AnalyticsRepository at com.package.TrackSpeed.execute()
Any suggestions?
Thanks in advance.

You could create an actual instance of Lazy that returns your mocked AnalyticsRepository in get():
analyticsRepository = mock<AnalyticsRepository>()
val lazy = Lazy<AnalyticsRepository> { analyticsRepository }
trackSpeed = TrackSpeed(lazy)

Related

How to mock methods of an activity annotated with #AndroidEntryPoint

I want to create a mock Object of an Activity with annotation #AndroidEntryPoint. After that mock it's methods like
whenever(activity.getAnalytics()).thenReturn(mockOfAnalytics)
but it doesn't work for activity annotated by #AndroidEntryPoint, when I remove this annotation it works - build.tool 4.2.2
#RunWith(PowerMockRunner::class)
#PrepareForTest(Html::class)
class CallShopViewModelTest {
...
#Mock
private lateinit var storefrontAnalytics: StorefrontAnalytics
#Mock
private lateinit var storefrontDelegate: StorefrontActivity.StorefrontDelegate
...
#Test
fun setShopObject() {
val mock = Mockito.mock(ShopMenuActivity::class.java)
whenever(mock.storefrontAnalytics).thenReturn(storefrontAnalytics)
whenever(mock.storefrontDelegate).thenReturn(storefrontDelegate)
whenever(mock.getString(ArgumentMatchers.anyInt(),
ArgumentMatchers.anyString())).thenReturn("test")
objectUnderTest = CallShopViewModel(mock)
objectUnderTest.setShop(Shop().apply {
isAcquired = false
shopId = 100
twilioPhone = "123"
})
Assert.assertFalse(objectUnderTest.mAcquired.get())
Assert.assertTrue(objectUnderTest.twilioFormattedText.get() != "")
}
}
Activity
#AndroidEntryPoint
class ShopMenuActivity : StorefrontActivity {
....
val storefrontAnalytics: StorefrontAnalytics
get() = app.storefrontAnalytics
val storefrontDelegates: StorefrontDelegates
get() = app.storefrontDelegates
}
So How can I mock this activity and use it's methods?
Thanks!
You better inject storefrontAnalytics: StorefrontAnalytics and val storefrontDelegates: StorefrontDelegates. Hilt plugin is rewriting your #AndroidEntryPoint annotated to a different decorator, this is probably the reason you are getting different results.
If you want to unit test your view model, you should not bring in the activity that hosts it. Provide mocked dependencies directly to the view model. Also why you are providing the activity to the view model constructor? This makes your unit testing excruciatingly painful.
Something like this:
#RunWith(PowerMockRunner::class)
#PrepareForTest(Html::class)
class CallShopViewModelTest {
...
#Mock
private lateinit var storefrontAnalytics: StorefrontAnalytics
#Mock
private lateinit var storefrontDelegate: StorefrontActivity.StorefrontDelegate
...
#Test
fun setShopObject() {
val mockStorefrontAnalytics : ... = mock()
val mockStorefrontDelegate : ... = mock()
whenever(mockStorefrontAnalytics).thenReturn(storefrontAnalytics)
whenever(mockStorefrontDelegate).thenReturn(storefrontDelegate)
objectUnderTest = CallShopViewModel(mockmockStorefrontAnalytics, mockStorefrontDelegate)
objectUnderTest.setShop(Shop().apply {
isAcquired = false
shopId = 100
twilioPhone = "123"
})
Assert.assertFalse(objectUnderTest.mAcquired.get())
Assert.assertTrue(objectUnderTest.twilioFormattedText.get() != "")
}
}

getting 'Missing calls inside every { ... } block' when using mokk

I'm using MockK as mock library for kotlin, for some reason I get an error on the every{} line.
io.mockk.MockKException: Missing calls inside every { ... } block.
this is my test class
internal class TestClass {
private val service: MyService = mockk(relaxed = true)
private val dao: MyDao = mockk(relaxed = true)
private val repository: MyRepository = MyRepositoryImp(
service = service,
dao = dao,
)
#Test
fun test() = runBlocking {
val expected = SOMETHING
every { repository.getValues() } returns flowOf(SOMETHINGELSE) // crash
assertEquals(1, 1)
}
}
MyService is just a retrofit interface
internal interface MyService {
#GET("123")
suspend fun getRemoteData(): Response<SOMECLASS>
}
Anyone has any idea? thanks!
You've instantiated the repository as a real instance. That means that it'll actually execute the code inside its implementation and that's known as the 'subject under test'.
But every { ... } returns ... needs a mockk instance.
So in your example, it should be
every { service.getRemoteData() } returns ...
or
every { dao.someMethod() } returns ...

Why API actually called when run unit test of a function used Coroutine to call API with networkBoundResource in Android?

I have an issue in unit test for a function used Coroutine to call API with networkBoundResource.
The issue is when run the test the API actually called, although it's supposed to return the expected response such as I determined in this line: whenever(mfSDKPaymentRepository.sendPayment(request)).thenReturn(expectedResponse)
This is the function want to test:
fun callSendPayment(
coroutineScope: CoroutineScope? = GlobalScope,
request: MFSendPaymentRequest,
apiLang: String,
listener: (MFResult<MFSendPaymentResponse>) -> Unit
) {
Const.apiLang = apiLang
coroutineScope?.launch(Dispatchers.Main) {
val dataResource = networkBoundResource {
mInteractors.sendPayment(request)
}
when (dataResource) {
is MFResult.Success ->
listener.invoke(MFResult.Success(dataResource.value.response!!))
is MFResult.Fail ->
listener.invoke(MFResult.Fail(dataResource.error))
}
}
}
This is the test class:
class MFSDKMainTest {
private val mfSDKPaymentRepository = mock<MFSDKPaymentGateWay>()
private val testScope = TestCoroutineScope()
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setup() {
Dispatchers.setMain(Dispatchers.Unconfined)
}
#After
fun tearDown() {
Dispatchers.resetMain()
testScope.cleanupTestCoroutines()
}
#Test
fun testCallSendPayment() = runBlockingTest {
val data = MFSendPaymentResponse(invoiceId = ID)
val expectedResponse = SDKSendPaymentResponse(data)
val request = MFSendPaymentRequest(
0.100,
"Customer name",
MFNotificationOption.LINK
)
val lang = MFAPILanguage.EN
whenever(mfSDKPaymentRepository.sendPayment(request))
.thenReturn(expectedResponse)
MFSDKMain.callSendPayment(testScope, request, lang) {
assert(it is MFResult.Success)
}
}
}
In your function when you call
mInteractors.sendPayment(request)
how do you get a refrence to MFSDKPaymentGateWay? In your test method you are not setting the mocked object in MFSDKMain class.
If you are creating it inside the same class (which i assume is an Object class) you may need to find a way to mock object class. Probably you need to use mockk instead of mockito
If you have setter method for MFSDKPaymentGateWay you should call it in your test method.

Android Unit Testing with Mockito for External REST API calls

I am new to JUnit test cases in Android. I heard that by using Mockito we can achieve easily.
My Android Class makes an external call to a REST API Service (Retrofit) that returns a JSON response. I have to mock that response (hardcoded JSON) and write test cases.
Please share your idea how to achieve this.
i am able to achieve.
updating with a piece of code.
class GenerateTripViewModelTest {
#Mock
private lateinit var mockRepository: GenerateTripRepository
private val schedulerProvider = SchedulerProvider(Schedulers.trampoline(), Schedulers.trampoline())
private lateinit var generateTripViewModel: GenerateTripViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
generateTripViewModel = GenerateTripViewModel(mockRepository, schedulerProvider)
}
#Test
fun SearchSuccessCase() {
val tripReq = GenerateTripReqModel(ArrayList<String>(),"123","xxxx","xxxx")
Mockito.`when`(mockRepository.generateTrip(tripReq)).thenReturn(Observable.just(GenerateTripResModel("")))
val testObserver = TestObserver<GenerateTripResModel>()
generateTripViewModel.generateTrip(tripReq).subscribe(testObserver)
testObserver.assertNoErrors()
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
class GenerateTripViewModel #Inject constructor(private val generateTripRepository: GenerateTripRepository,private val schedulerProvider: SchedulerProvider) : ViewModel() {
fun generateTrip(reqModel: GenerateTripReqModel) = generateTripRepository.generateTrip(reqModel)
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#Singleton
class GenerateTripRepository #Inject constructor(
private val apiServices: ApiServices, private val context: Context,
private val appExecutors: AppExecutors = AppExecutors()) {
/**
* Generate Trip
*/
fun generateTrip(reqModel: GenerateTripReqModel): Observable<GenerateTripResModel> = apiServices.generateTrip(reqModel)
}
class Test{
#Mock
lateinit var redditApiService: RedditApiService
lateinit var postSettingsViewModel: PostSettingsViewModel
#Before
fun setUp() {
initMocks(this)
postSettingsViewModel = PostSettingsViewModel(redditApiService, userRepo)
}
#Test
fun testApi(){
Mockito.`when`(redditApiService.getSubreddits("asd")).thenReturn(Single
.just<SubredditResponse>(SubredditResponse(listOf(Subreddit("first")))))
//make your tests
}
}
You can use viewmodel or presenter and pass your api service there. With mockito you can specify function call and its return value. Mockito.when(api.get()).thenReturn(new Result()).
You can check google samples: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/test/java/com/android/example/github/api/GithubServiceTest.kt
You can use product flavors for mock your response or user MockWebServer library or replace your retrofit interface with you implementation what return json from assets
upd: I misunderstood the question. I agree with Phowner Biring

Mocking another parameter on function not working when one of them is a callback interface

I successfully did some tests of asynchronous function with only one callback interface as parameter with mockito-kotlin library but when I try to do a test of same function with another parameter like a String or Integer I receive error:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.example.presentation.presenter.MyCollectionPresenterTest.getComicListByHeroOK(MyCollectionPresenterTest.kt:97)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
I´m sure I´m mocking properly callback interface with any() but I don´t know if I´m mocking integer parameter correctly. I tried with any(), anyInt(), eq(1) and any() as Int but always the same error.
Here is the class that I want to test:
#PerFragment
class MyCollectionPresenter #Inject constructor(private val useCase: GetComicListByHeroUseCase) {
#Inject
lateinit var view: MyCollectionView
lateinit var models: List<ComicModel>
fun getComicListByHero(heroId: Int) {
useCase.execute(heroId, object : HeroUseCase.GetComicListByHeroCallback {
override fun onComicListReceived(comicList: List<Comic>) {
models = ComicModelMapper.toModel(comicList)
view.setItems(models)
}
override fun onError() {
view.showMessage()
}
})
}
}
And this is the test class:
class MyCollectionPresenterTest : UnitTest() {
private lateinit var presenter: MyCollectionPresenter
#Mock
private lateinit var useCase: GetComicListByHeroUseCase
#Mock
private lateinit var view: MyCollectionView
#Before
fun setUp() {
presenter = MyCollectionPresenter(useCase)
initializeView()
}
#Test
fun getComicListByHeroOK() {
setupGetComicsCallbackOK()
presenter.getComicListByHero(any())
verify(presenter.view).setItems(emptyList())
}
#Test
fun getComicListByHeroError() {
setupGetComicsCallbackError()
presenter.getComicListByHero(any())
verify(presenter.view).showMessage()
}
private fun initializeView() {
presenter.view = view
}
private fun setupGetComicsCallbackError() {
doAnswer {
val callback = it.arguments[0] as HeroUseCase.GetComicListByHeroCallback
callback.onError()
null
}.`when`(useCase).execute(any(), any())
}
private fun setupGetComicsCallbackOK() {
doAnswer {
val callback = it.arguments[0] as HeroUseCase.GetComicListByHeroCallback
callback.onComicListReceived(emptyList())
null
}.`when`(useCase).execute(any(), any())
}
}
This is base unit test class:
#RunWith(MockitoJUnitRunner::class)
abstract class UnitTest {
#Suppress("LeakingThis")
#Rule
#JvmField
val injectMocks = InjectMocksRule.create(this#UnitTest)
}
And this is a class for inject mocks rule:
class InjectMocksRule {
companion object {
fun create(testClass: Any) = TestRule { statement, _ ->
MockitoAnnotations.initMocks(testClass)
statement
}
}
}
Thank you very much for your help and excuse my english.
Regards!
UPDATE: I found the solution and posted as answer.
Finally, I know what I was doing wrong. First at all, it.argument[1] because callback is the second parameter of the function that I want to mock the answer.
And the other thing is that I was mocking the parameter of the function that I want to test presenter.getComicListByHero(1).
Here is the revised code:
#Test
fun getComicListByHeroError() {
setupGetComicsCallbackError()
presenter.getComicListByHero(1)
verify(presenter.view).showMessage()
}
private fun setupGetComicsCallbackError() {
doAnswer {
val callback = it.arguments[1] as HeroUseCase.GetComicListByHeroCallback
callback.onError()
null
}.`when`(useCase).execute(ArgumentMatchers.anyInt(), any())
}
Thank you very much to #voghDev for his help

Categories

Resources