Robolectric start a fragment that has an observer - android

How to start a fragment with a LiveData observer in the test scope with Robolectric
Fragment
class MyFragment(private val viewModel: MyViewModel) : Fragment() {
...
fun myObserver {
...
// If I remove this observer the test will pass.
viewModel.MyLiveData.observe(viewLifecycleOwner, Observer{
...
}
}
}
My Test uses the RobolectricTestRunner so I can launch the fragment in the test scope.
#RunWith(robolectricTestRunner::class)
class MyFragmentTest {
// Executes tasks in the Architecture Components in the same thread
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Test
fun testOne() {
val viewModel: MyViewModel = mock(MyViewModel::class.java)
val scenario = launchFragmentInContainer(
factory = MainFragmentFactory(viewModel),
fragmentArgs = null
themeResId = R.style.Theme_MyTheme
)
// Tried implementing shadowOf as the error suggests.
}
}
I get the following error when trying to run the test. I've tried setting Main looper to idle before and after instantiating the FragmentScenario.
java.lang.Exception: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(getMainLooper()).idle() call.
I've tried the following
implementing a shadow class for the Main Looper. Annotating the class with Looper mode.
#RunWith(RobolectricTestRunner::class)
#LooperMode(LooperMode.Mode.PAUSED)
class MyFragmentTest {
Adding scenario states
scenario.moveToState(Lifecycle.State.CREATED)
scenario.moveToState(Lifecycle.State.RESUMED)
My Test dependencies.
// Test
testImplementation 'androidx.arch.core:core-testing:2.1.0'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3'
testImplementation "androidx.test.ext:junit-ktx:1.1.2"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3"
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test.espresso:espresso-core:3.3.0'
testImplementation "org.robolectric:robolectric:4.5.1"
testImplementation "org.mockito:mockito-android:2.28.2"
// Testing Fragments
debugImplementation "androidx.fragment:fragment-testing:1.3.2"
Links I've used to find a solution'
Testing LiveData Transformations?
https://jeroenmols.com/blog/2019/01/17/livedatajunit5/

I took a look into your repository on github here. Here's what I've found.
Problem 1
Your first problem is that you mock out a ViewModel. So, when you simulate onResume for your Fragment it invokes:
fun liveDataObserver() {
viewModel.scoreLiveData.observe(viewLifecycleOwner, {
//
} )
}
Since viewModel is mocked, scoreLiveData is null and you get an NPE.
To fix this, you also mock out scoreLiveData method so that it returns some acceptable result:
...
val liveData = MutableLiveData<Int>().apply { value = 3 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
...
This will fix your testOne completely, but not yet testTwo.
Problem 2
It's related only to your testTwo method. The problem is that you're calling liveDataObserver() in your also block, and that is invoked before your Fragment's viewLifecycleOwner has been set in onCreateView:
...
scenario = launchFragmentInContainer {
MyFragment(viewModel).also {
it.liveDataObserver()
}
}
...
I'm not sure what exactly you're trying to test here, but if you want to verify that you can start observing after Fragment's View has been created, you could do something as following:
...
// make sure your Fragment is started
scenario = launchFragmentInContainer (
factory = MainFragmentFactory(viewModel),
initialState = Lifecycle.State.STARTED
)
// call liveDataObserver on it
scenario.withFragment {
this.liveDataObserver()
}
Full Code
#RunWith(RobolectricTestRunner::class)
class MyFragmentTest {
private lateinit var scenario: FragmentScenario<MyFragment>
#Test
fun testOne() = runBlockingTest {
val liveData = MutableLiveData<Int>().apply { value = 1 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
scenario = launchFragmentInContainer(
factory = MainFragmentFactory(viewModel),
fragmentArgs = null,
themeResId = R.style.Theme_TDDScoreKeeper,
initialState = Lifecycle.State.STARTED
)
scenario.moveToState(Lifecycle.State.RESUMED)
scenario.recreate() // Simulates if the phone ran low on resources and the app had to be recreated.
}
#Test
fun testTwo() {
val liveData = MutableLiveData<Int>().apply { value = 1 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
scenario = launchFragmentInContainer(
factory = MainFragmentFactory(viewModel),
initialState = Lifecycle.State.STARTED
)
scenario.withFragment {
this.liveDataObserver()
}
}
}

Related

How to mock LiveData in mockk kotlin

Hey I am using mockk library in my project. I am trying to mock MutableLiveData, but it always give me null value. Can someone guide me how to do in proper way.
Suppose I have one function
var dataLiveData = MutableLiveData<Boolean>()
val currentDeviceTypeLiveData = MutableLiveData<Boolean>()
internal fun handleDataResponse() {
dataLiveData.postValue(true)
currentDeviceTypeLiveData.postValue(true)
}
I am trying to test
#Test
fun `handleDataResponse - Handle connection success `() {
// STUBBING
// EXECUTION
viewModel.handleDataResponse()
// VERIFICATION
assertEquals(true, viewModel.dataLiveData.value)
assertEquals(true, viewModel.currentDeviceTypeLiveData.value)
}
It gives me this when I run the test
Expected : true
Actual :null
dependencies
testImplementation 'androidx.arch.core:core-testing:2.1.0'
testImplementation "io.mockk:mockk:1.12.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
Some tips that helped me:
run your test in runBlockingTest
create variable for live data before sending in assert function
#Test
fun `handleDataResponse - Handle connection success `() {
runBlockingTest{
// STUBBING
// EXECUTION
viewModel.handleDataResponse()
val dataLiveData = viewModel.dataLiveData.value
val currentDeviceTypeLiveData = viewModel.currentDeviceTypeLiveData.value
// VERIFICATION
assertEquals(true, dataLiveData)
assertEquals(true, currentDeviceTypeLiveData)
}
}
Add this in test Class
#get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
Declare
private val testCoroutineDispatcher = TestCoroutineDispatcher()
and Add this in setUp()
Dispatchers.setMain(testCoroutineDispatcher)
Add this in tearDown()
Dispatchers.resetMain()
Add this before your test class declaration
#ExperimentalCoroutinesApi

Android instrumented test freezes when it tests a suspend function that uses RoomDatabase.withTransaction

I'm trying to test the following LocalDataSource function, NameLocalData.methodThatFreezes function, but it freezes. How can I solve this? Or How can I test it in another way?
Class to be tested
class NameLocalData(private val roomDatabase: RoomDatabase) : NameLocalDataSource {
override suspend fun methodThatFreezes(someParameter: Something): Something {
roomDatabase.withTransaction {
try {
// calling room DAO methods here
} catch(e: SQLiteConstraintException) {
// ...
}
return something
}
}
}
Test class
#MediumTest
#RunWith(AndroidJUnit4::class)
class NameLocalDataTest {
private lateinit var nameLocalData: NameLocalData
// creates a Room database in memory
#get:Rule
var roomDatabaseRule = RoomDatabaseRule()
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setup() = runBlockingTest {
initializesSomeData()
nameLocalData = NameLocalData(roomDatabaseRule.db)
}
#Test
fun methodThatFreezes() = runBlockingTest {
nameLocalData.methodThatFreezes // test freezes
}
// ... others NameLocalDataTest tests where those functions been tested does not use
// roomDatabase.withTransaction { }
}
Gradle's files configuration
espresso_version = '3.2.0'
kotlin_coroutines_version = '1.3.3'
room_version = '2.2.5'
test_arch_core_testing = '2.1.0'
test_ext_junit_version = '1.1.1'
test_roboletric = '4.3.1'
test_runner_version = '1.2.0'
androidTestImplementation "androidx.arch.core:core-testing:$test_arch_core_testing"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.ext:junit:$test_ext_junit_version"
androidTestImplementation "androidx.test:rules:$test_runner_version"
androidTestImplementation "androidx.test:runner:$test_runner_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
Last time I wrote a test for Room database I just simply use runBlock and it worked for me...
Could you take a look into this sample and check if it works for you as well?
Edit:
Ops! I missed this part... I tried this (in the same sample):
I defined a dummy function in my DAO using #Transaction
#Transaction
suspend fun quickInsert(book: Book) {
save(book)
delete(book)
}
I think this is the key of the problem. Add setTransactionExecutor to your Database instantiation.
appDatabase = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
AppDatabase::class.java
).setTransactionExecutor(Executors.newSingleThreadExecutor())
.build()
Finally, the test worked using runBlocking
#Test
fun dummyTest() = runBlocking {
val dao = appDatabase.bookDao();
val id = dummyBook.id
dao.quickInsert(dummyBook)
val book = dao.bookById(id).first()
assertNull(book)
}
See this question.
I had tried many things to make this work, used runBlockingTest, used TestCoroutineScope, tried runBlocking, used allowMainThreadQueries, setTransactionExecutor, and setQueryExecutor on my in memory database.
But nothing worked until I found this comment thread in the Threading models in Coroutines and Android SQLite API article in the Android Developers Medium blog, other people mentioned running into this. Author Daniel Santiago said:
I’m not sure what Robolectric might be doing under the hood that could cause withTransaction to never return.
We usually don’t have Robolectric tests but we have plenty of Android Test examples if you want to try that route: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
I was able to fix my test by changing it from a Robolectric test to an AndroidTest and by using runBlocking
This is an example from the google source:
#Before
#Throws(Exception::class)
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
TestDatabase::class.java
)
.build()
booksDao = database.booksDao()
}
#Test
fun runSuspendingTransaction() {
runBlocking {
database.withTransaction {
booksDao.insertPublisherSuspend(
TestUtil.PUBLISHER.publisherId,
TestUtil.PUBLISHER.name
)
booksDao.insertBookSuspend(TestUtil.BOOK_1.copy(salesCnt = 0))
booksDao.insertBookSuspend(TestUtil.BOOK_2)
booksDao.deleteUnsoldBooks()
}
assertThat(booksDao.getBooksSuspend())
.isEqualTo(listOf(TestUtil.BOOK_2))
}
}

Unit testing coroutines runBlockingTest: This job has not completed yet

Please find below a function using a coroutine to replace callback :
override suspend fun signUp(authentication: Authentication): AuthenticationError {
return suspendCancellableCoroutine {
auth.createUserWithEmailAndPassword(authentication.email, authentication.password)
.addOnCompleteListener(activityLifeCycleService.getActivity()) { task ->
if (task.isSuccessful) {
it.resume(AuthenticationError.SignUpSuccess)
} else {
Log.w(this.javaClass.name, "createUserWithEmail:failure", task.exception)
it.resume(AuthenticationError.SignUpFail)
}
}
}
}
Now I would like to unit testing this function. I am using Mockk :
#Test
fun `signup() must be delegated to createUserWithEmailAndPassword()`() = runBlockingTest {
val listener = slot<OnCompleteListener<AuthResult>>()
val authentication = mockk<Authentication> {
every { email } returns "email"
every { password } returns "pswd"
}
val task = mockk<Task<AuthResult>> {
every { isSuccessful } returns true
}
every { auth.createUserWithEmailAndPassword("email", "pswd") } returns
mockk {
every { addOnCompleteListener(activity, capture(listener)) } returns mockk()
}
service.signUp(authentication)
listener.captured.onComplete(task)
}
Unfortunately this test failed due to the following exception : java.lang.IllegalStateException: This job has not completed yet
I tried to replace runBlockingTest with runBlocking but the test seems to wait in an infinite loop.
Can someone help me with this UT please?
Thanks in advance
As can be seen in this post:
This exception usually means that some coroutines from your tests were scheduled outside the test scope (more specifically the test dispatcher).
Instead of performing this:
private val networkContext: CoroutineContext = TestCoroutineDispatcher()
private val sut = Foo(
networkContext,
someInteractor
)
fun `some test`() = runBlockingTest() {
// given
...
// when
sut.foo()
// then
...
}
Create a test scope passing test dispatcher:
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
private val networkContext: CoroutineContext = testDispatcher
private val sut = Foo(
networkContext,
someInteractor
)
Then in test perform testScope.runBlockingTest
fun `some test`() = testScope.runBlockingTest {
...
}
See also Craig Russell's "Unit Testing Coroutine Suspend Functions using TestCoroutineDispatcher"
In case of Flow testing:
Don't use flow.collect directly inside runBlockingTest. It should be wrapped in launch
Don't forget to cancel TestCoroutineScope in the end of a test. It will stop a Flow collecting.
Example:
class CoroutinesPlayground {
private val job = Job()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(job + testDispatcher)
#Test
fun `play with coroutines here`() = testScope.runBlockingTest {
val flow = MutableSharedFlow<Int>()
launch {
flow.collect { value ->
println("Value: $value")
}
}
launch {
repeat(10) { value ->
flow.emit(value)
delay(1000)
}
job.cancel()
}
}
}
This is not an official solution, so use it at your own risk.
This is similar to what #azizbekian posted, but instead of calling runBlocking, you call launch.
As this is using TestCoroutineDispatcher, any tasks scheduled to be run without delay are immediately executed. This might not be suitable if you have several tasks running asynchronously.
It might not be suitable for every case but I hope that it helps for simple cases.
You can also follow up on this issue here:
https://github.com/Kotlin/kotlinx.coroutines/issues/1204
If you know how to solve this using the already existing runBlockingTest and runBlocking, please be so kind and share with the community.
class MyTest {
private val dispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(dispatcher)
#Test
fun myTest {
val apiService = mockk<ApiService>()
val repository = MyRepository(apiService)
testScope.launch {
repository.someSuspendedFunction()
}
verify { apiService.expectedFunctionToBeCalled() }
}
}
According to my understanding, this exception occurs when you are using a different dispatcher in your code inside the runBlockingTest { } block with the one that started runBlockingTest { }.
So in order to avoid this, you first have to make sure you inject Dispatchers in your code, instead of hardcoding it throughout your app. If you haven't done it, there's nowhere to begin because you cannot assign a test dispatcher to your test codes.
Then, in your BaseUnitTest, you should have something like this:
#get:Rule
val coroutineRule = CoroutineTestRule()
#ExperimentalCoroutinesApi
class CoroutineTestRule(
val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher() {
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.setMain(testDispatcher)
}
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
Next step really depends on how you do Depedency Injection. The main point is to make sure your test codes are using coroutineRule.testDispatcher after the injection.
Finally, call runBlockingTest { } from this testDispatcher:
#Test
fun `This should pass`() = coroutineRule.testDispatcher.runBlockingTest {
//Your test code where dispatcher is injected
}
There is an open issue for this problem: https://github.com/Kotlin/kotlinx.coroutines/issues/1204
The solution is to use the CoroutineScope intead of the TestCoroutinScope until the issue is resolved, you can do by replacing
#Test
fun `signup() must be delegated to createUserWithEmailAndPassword()`() =
runBlockingTest {
with
#Test
fun `signup() must be delegated to createUserWithEmailAndPassword()`() =
runBlocking {
None of these answers quite worked for my setup due to frequent changes in the coroutines API.
This specifically works using version 1.6.0 of kotlin-coroutines-test, added as a testImplementation dependency.
#Test
fun `test my function causes flow emission`() = runTest {
// calling this function will result in my flow emitting a value
viewModel.myPublicFunction("1234")
val job = launch {
// Force my flow to update via collect invocation
viewModel.myMemberFlow.collect()
}
// immediately cancel job
job.cancel()
assertEquals("1234", viewModel.myMemberFlow.value)
}
If you have any
Channel
inside the launch, you must call to
Channel.close()
Example code:
val channel = Channel<Success<Any>>()
val flow = channel.consumeAsFlow()
launch {
channel.send(Success(Any()))
channel.close()
}
runBlockingTest deprecated since 1.6.0 and replaced with runTest.
You need to swap arch background executor with one that execute tasks synchronously. eg. For room suspend functions, live data etc.
You need the following dependency for core testing
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
Then add the following at the top of test class
#get:Rule
val instantExecutor = InstantTaskExecutorRule()
Explanations
InstantTaskExecutorRule A JUnit Test Rule that swaps the background executor used by the
Architecture Components with a different one which executes each task
synchronously.
You can use this rule for your host side tests that use Architecture
Components
As I mentioned here about fixing runBlockingTest, maybe it could help you too.
Add this dependency if you don't have it
testImplementation "androidx.arch.core:core-testing:$versions.testCoreTesting" (2.1.0)
Then in your test class declare InstantTaskExecutorRule rule:
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()

Mockito.verify didn't see method exectution, even if it was

The error I have:
The code with the error:
#RunWith(PowerMockRunner::class)
#PrepareForTest(PotatoProvider::class, PotatoConsumer::class)
class WantedButNotInvoked {
#Mock
lateinit var potatoConsumer: PotatoConsumer
#Test
fun potato() {
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
//verify(potatoConsumer).accept(any()) //-> This fails too with the same reason
}
}
data class Potato(val value: Int = 1)
class PotatoConsumer : Consumer<Potato> {
override fun accept(t: Potato?) {
println(t)
}
}
So I making subscribe with this mock(potatoConsumer), and the rxJava have called 'accept', and mockito mark it as interaction, but mockito thinks this interaction is not what I'm expecting, why?
Versions of libraries used her:
mockitoVersion = '2.8.9'
mockitoAndroidVersion = '2.7.22'
powerMockVersion="2.0.2"
kotlinMockito="2.1.0"
rxKotlin = "2.3.0"
rxJavaVersion = "2.2.10"
Kinda workaround
Some fields mocked by powermock, fails on 'verify', but fields mocked with mockito is not;
Mockito can't mock not opened fields, without mock-maker-inline, but mockito conflicts with Powermock mock-maker-inline;
Powermock can delegate calls of mock-maker-inline to other mock-maker-inline(https://github.com/powermock/powermock/wiki/PowerMock-Configuration)
Use Mockito.mock on the failed fields instead of #Mock/Powermock mock injection
Example of the "green" potato test method using PowerMockRunner
#Test
fun potato() {
potatoConsumer = mock() // <-
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(potato)
}
I am not familiar with PowerMock but I tried this test and it passes:
#Test
fun potato() {
fakePotatoProvider = Mockito.mock(PotatoProvider::class.java)
potatoConsumer = Mockito.mock(PotatoConsumer::class.java)
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(Potato()))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
}
Maybe because you aren't passing the same instance of Potato(). Try to refactor your code to this
#Test
fun potato() {
val testPotato = Potato()
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(testPotato))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(testPotato)
}
As I mentioned above, the reason why it might be failing is the constant creation of new instances when passing your Potato object, hance that comparison fails.

Why do my Android unit tests fail when run together, but pass when run individually?

Here is my whole test class:
#RunWith(JUnit4::class)
class ExplorerRemoteImplTest {
// Mocks
private val mockDatabase = mock<FirebaseFirestore>(
defaultAnswer = RETURNS_DEEP_STUBS
)
private val mockQuerySnapshot = mock<QuerySnapshot>()
private val mockQuerySnapshotTask = mock<Task<QuerySnapshot>>()
// Class under test
private lateinit var explorerRemoteImpl: ExplorerRemoteImpl
// Others
private val poiList = listOf(TestDataFactory.makePoiRepo(),TestDataFactory.makePoiRepo())
#Before
fun setup(){
//create instance of class under test
explorerRemoteImpl = ExplorerRemoteImpl(mockDatabase)
// Step #1 return the query Task on get().
whenever(mockDatabase.collection(ArgumentMatchers.anyString()).orderBy(ArgumentMatchers.anyString()).get()).thenReturn(mockQuerySnapshotTask)
// Step #2 return a queryTask when registering the listener
whenever(mockQuerySnapshotTask.addOnCompleteListener(anyOrNull()))
.thenReturn(mockQuerySnapshotTask)
// Step #3 task IS successful is stubbed
// Step #4 the results of the task is a QuerySnapshot
whenever(mockQuerySnapshotTask.result).thenReturn(mockQuerySnapshot)
// Step #5 QuerySnapshot = is empty or not is stubbed
// Step #6 when we try to convert snapShot to objects
whenever(mockQuerySnapshot.toObjects(PoiRepository::class.java)).thenReturn(poiList)
}
private fun stubQuerySnapshotIsEmpty(boolean: Boolean){
whenever(mockQuerySnapshot.isEmpty).thenReturn(boolean)
}
private fun stubQueryTaskIsSuccessful(boolean: Boolean){
whenever(mockQuerySnapshotTask.isSuccessful).thenReturn(boolean)
}
#After
fun onEnd(){
Mockito.reset(mockQuerySnapshotTask)
Mockito.reset(mockDatabase)
Mockito.reset(mockQuerySnapshot)
}
#Test
fun getPoisCompletes() {
// GIVEN
stubQueryTaskIsSuccessful(true)
stubQuerySnapshotIsEmpty(false)
val testObserver = explorerRemoteImpl.getPois().test()
// Trigger callback reply
// see: https://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/
val captor = argumentCaptor<OnCompleteListener<QuerySnapshot>>()
verify(mockQuerySnapshotTask).addOnCompleteListener(captor.capture())
captor.lastValue.onComplete(mockQuerySnapshotTask)
verify(mockQuerySnapshotTask, times(1)).addOnCompleteListener(anyOrNull())
// THEN
testObserver
.assertNoErrors()
.assertValueCount(1)
.assertComplete()
}
#Test
fun getPoisCompletesOnEmptyQuerySnapshot() {
// GIVEN
stubQueryTaskIsSuccessful(true)
stubQuerySnapshotIsEmpty(true)
val testObserver = explorerRemoteImpl.getPois().test()
// Trigger callback reply
// see: https://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/
val captor = argumentCaptor<OnCompleteListener<QuerySnapshot>>()
verify(mockQuerySnapshotTask).addOnCompleteListener(captor.capture())
captor.firstValue.onComplete(mockQuerySnapshotTask)
// THEN
testObserver
.assertNoErrors()
.assertValueCount(0)
.assertComplete()
Mockito.verify(mockQuerySnapshotTask, times(1)).addOnCompleteListener(anyOrNull())
}
#Test
fun getPoisErrorsOnNoSuccessQuerySnapshot() {
// GIVEN
stubQueryTaskIsSuccessful(false)
stubQuerySnapshotIsEmpty(true)
val testObserver = explorerRemoteImpl.getPois().test()
// Trigger callback reply
val captor = argumentCaptor<OnCompleteListener<QuerySnapshot>>()
verify(mockQuerySnapshotTask).addOnCompleteListener(captor.capture())
captor.firstValue.onComplete(mockQuerySnapshotTask)
Mockito.verify(mockQuerySnapshotTask, times(1)).addOnCompleteListener(anyOrNull())
// task Exception not mocked, so unknown is passed via Elvis operator
testObserver.assertError(UnknownError::class.java)
}
}
There are 3 unit tests and they all pass when run separately, but when I run the whole test class my 2nd and 3rd tests fail with an error that reads:
Wanted but not invoked:
task.addOnCompleteListener(
<Capturing argument>
);
-> at com.loc8r.seattleexplorer.remote.ExplorerRemoteImplTest.getPoisCompletesOnEmptyQuerySnapshot(ExplorerRemoteImplTest.kt:113)
Actually, there were zero interactions with this mock.
I've tried everything I can think of to resolve the problem:
I moved my class under test instantiation into the #Before function.
I tried creating an #After function and called Mockito.reset on my mocks.
I should mention that I'm using the nhaarman.mockitokotlin2 library and it's argumentCaptor.
Any clues as to why these tests passing when run alone but failing when run together as a class?
Have you tried to avoid using a single, shared instance of the Class Under Tests which is ExplorerRemoteImpl in you case? Try to create a new instance for every single test method.
Actually I am facing exactly the same issue that you have described above, but I cannot re-instantiate my Class Under Test, because I try to test a singleton.
Update:
I have refactored the implementation of my singleton so for testing I can instantiate the Class Under Test for every single test method. The issue does no longer occur.

Categories

Resources