How to unit test a callback in parameter - Kotlin - java

I have a Client class (written in Kotlin in an Android app) that implements an interface ReadyCallback (written in Java in a library of the app, the app is dependent on this library). In Client I have a createClient() method which will create a client with the parameter of ReadyCallback. If it's ready, I will perform other tasks by calling classC.otherMethod(), if not ready, I just create the client without doing other stuff:
In the library:
// Somewhere in this library, I have logic to call `readyCallback.onReady()` when I consider it's "ready"
interface ReadyCallback {
void onReady()
}
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady();
}
}
}
In the app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { onReady() }
}
override fun onReady() {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
In unit test, I want to verify that when I create the client and it's ready, classC's otherMethod() will be invoked. I tried to do the following but it's not correct:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
#Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
#Test
fun `when create client then call otherMethod`() {
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
}
The error message shows:
Wanted but not invoked:
classC.otherMethod();
Actually, there were zero interactions with this mock.
I think the reason I got this error is because, if I don't call getReadyCallback(), it means I am not invoking the callback, so there's no call to classC.otherMethod(). But other than that I am really stuck on this, I don't know how to unit test my desire behavior (If it's ready, classC.otherMethod() will be called, if not ready, this method won't be called).
I know I can't do things like below because unitUnderTest is not a mock object:
callbackMock = mock()
whenever(unitUnderTest.getReadyCallback()).thenReturn(callbackMock)
whenever(clientProviderMock.create(callbackMock).thenReturn(client)
Can anyone help me out please?

The only way I can think of is to add a boolean flag in callback's onReady() method. So it will become:
In library:
interface ReadyCallback {
void onReady(final boolean isReady)
}
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady(true);
} else {
readyCallback.onReady(false);
}
}
}
In app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { isReady -> onReady(isReady) }
}
override fun onReady(isReady: Boolean) {
if (isReady) {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
}
In unit test:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
#Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
#Test
fun `when create client and ready then call otherMethod`() {
unitUnderTest.onReady(true)
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
#Test
fun `when create client and not ready then do not call otherMethod`() {
unitUnderTest.onReady(false)
unitUnderTest.createClient()
verifyZeroInteractions(classCMock)
}
}
But I still don't know how to test without the boolean parameter in the callback's method. Does anyone know how to do that?

I think I figured it out. I don't need a parameter in onReady().
In library:
interface ReadyCallback {
void onReady()
}
// place to determine when is "ready"
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady();
}
}
}
In app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { onReady() }
}
override fun onReady() {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
In unit test:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
#Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
#Test
fun `when create client and ready then call otherMethod`() {
unitUnderTest.onReady()
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
#Test
fun `when create client and not ready then do not call otherMethod`() {
unitUnderTest.createClient()
verifyZeroInteractions(classCMock)
}
}

Related

How to mock an instance method on injected instance of the class under test?

I am trying to mock an instance method within a real object which is injected within my Test class in spock and I am using Micronaut (with Java) for writing my application. It is as follows:
class MyTestClass extends Specification {
#Subject
#Inject
ClassUnderTest classUnderTest
Event sampleEvent = getMockEvent();
def "test1" () {
given:
def classUnderTestSpy = Spy(classUnderTest)
when:
def res = classUnderTestSpy.callTestMethod(sampleEvent)
then:
1 * classUnderTestSpy.isRunning(_ as Long) >> {
return false
}
res != null
}
def getMockEvent() {
new Event(/* Some attributes here */)
}
}
The ClassUnderTest is something like this:
#Singleton
class ClassUnderTest {
#Inject
Class1 class1Instance;
#Value("${com.myapplication.property}")
Integer value;
Object callTestMethod(Event event) {
// Some code
boolean isRunning = isRunning(event.id);
// Rest of the code
}
public boolean isRunning(Long id) {
return SomeOtherClass.staticMethod(id);
}
}
Whenever the isRunning method is called the real SomeOtherClass.staticMethod() method call happens and the isRunning method returns true not false. Is there anyway I can Spy the injected ClassUnderTest instance and mock the instance method?
Again, cannot reproduce your issue.
Based on your example, I did the following and the test passes:
#MicronautTest //Your example is missing this, cannot even test without it
class SpyTest extends Specification{
#Subject
#Inject
ClassUnderTest classUnderTest
Event sampleEvent = getMockEvent();
def "Spy Test"() {
given:
def classUnderTestSpy = Spy(classUnderTest)
when:
def results = classUnderTestSpy.callTestMethod(sampleEvent)
then:
// 1 * classUnderTestSpy.isRunning(_ as Long) >> false //Preferred Groovy/Spock way
1 * classUnderTestSpy.isRunning(_ as Long) >> {
println("I am returning the correct thing!")
false
}
results == "Is something running: false"
}
def getMockEvent() {
new Event("Is something running: ")
}
}
#Singleton
public class ClassUnderTest {
private final InjectedClass iClass; //Noise in the example, not used
public ClassUnderTest(InjectedClass iClass) { //Preferred way to inject
this.iClass = iClass;
}
public String callTestMethod(Event event) {
System.out.println("ClassUnderTest callTestMethod");
boolean running = isRunning(0L);
return event.message() + running;
}
public boolean isRunning(Long id) {
System.out.println("ClassUnderTest isRunning with ID: " + id);
return SomeOtherClass.staticMethod();
}
}
#Singleton
public class InjectedClass {
public String info() {
return "I was injected";
}
}
public record Event(String message) {}
#Singleton
public class SomeOtherClass {
public static boolean staticMethod() {
System.out.println("SomeOtherClass Static Method called");
throw new RuntimeException("Should never get called");
}
}

Mock return on a static extension method in Kotlin

How do I force a mocked return on a static extension method in Kotlin?
I have a data class called Job
data class Job(
val id: ObjectId) {
}
and a Mapper object with an extension method that's static for the Job class.
object Mapper {
Job.toOtherJob(): OtherJob.Job {
// Do work
}
}
The issue is that toOtherJob method calls the real one, and I cannot force a doReturn on it.
Test class
class ServiceTests {
#Mock
private val job: Job? = null
#BeforeAll
fun setUp() {
// Initialize mocks.
MockitoAnnotations.openMocks(this)
}
#AfterAll
fun cleanUp() {
// Cleanup mocks.
MockitoAnnotations.openMocks(this).close()
}
fun testJob() {
val otherJob = OtherJob()
whenever(job.toOtherJob()).thenReturn(otherJob)
// Act
job.toOtherJob() // <- This calls the real method, does not get mocked.
}
}

RxJava Unit test Observable interval, Change response by advancing through time

I have a network polling function with Observable interval in my repository
class repository #Inject constructor(
private val api: api,
private val schedulerProvider: SchedulerProvider
) {
private fun networkPoll(): Observable<response> {
return Observable.interval(1L, TimeUnit.Seconds, schedulerProvider.io())
.startWith(0L)
.flatMap {
api.getStatus().subscribeOn(schedulerProvider.io()).toObservable()
}
}
private fun pollStates(): Observable<State> {
return networkPoll()
.map {
// map the response to State1, State2, State3
}
}
fun pollForState3(): Observable<State> {
return pollStates()
.subscribeOn(schedulerProvider.io())
.takeUntil {
it is State3
}
}
}
How do I unit test pollForState3 and change the response by advancing through time?
I solved it by doing this
private lateinit var repository: Repository
private val schedulerProvider = TestSchedulerProvider()
#Mock
private lateinit var api: Api
#Before
fun setup() {
repository = repository(api, schedulerProvider)
}
#Test
fun test() {
`when`(api.getStatus()).thenReturn(
// return such a way that pollStates function map this to State1
)
val testObserver = repository.pollForState3().test()
schedulerProvider.ioScheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS)
testObserver.assertValue {
it is State1
}
testObserver.assertNotTerminated()
`when`(api.getStatus()).thenReturn(
// now return such a way that pollStates function map this to State3
)
schedulerProvider.ioScheduler.advanceTimeBy(2L, TimeUnit.SECONDS)
testObserver.assertValue {
it is State3
}
testObserver.assertTerminated()
testObserver.dispose()
}
}

cannot find implementation for RoomDatabase while injecting ViewModel into Fragment with Factory

I am struggling with viewmodel injection. I have been following tutorials and changed the code a little bit in order to adjust it to my needs, but the app crashes.
I have App class holding my DaggerComponent with it's modules. Inside it's onCreate I have:
component = DaggerAppComponent.builder().daoModule(DaoModule(this)).build()
My AppModule:
#Singleton
#Component(modules = [DaoModule::class, ViewModelModule::class])
interface AppComponent {
val factory: ViewModelFactory
}
ViewModelModule :
#Module
abstract class ViewModelModule {
#Binds
#Singleton
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#Singleton
#IntoMap
#ViewModelKey(TaskViewModel::class)
abstract fun splashViewModel(viewModel: TaskViewModel): ViewModel
}
MyFactory:
#Singleton
class ViewModelFactory #Inject constructor(
private val viewModels: MutableMap<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
viewModels[modelClass]?.get() as T
}
I used here ViewModelKey, ViewModelModule and Factory, and Fragment extension function to perform Fragment viewmodel injection. I found it online and used it succesfuly on previous projects. This is my util function:
#MainThread
inline fun <reified VM : ViewModel> Fragment.daggerViewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this }
) = createViewModelLazy(
VM::class,
{ ownerProducer().viewModelStore },
{ App.component.factory }
)
And my DaoModule.
#Module
class DaoModule(private val app: Application) {
#Provides
#Singleton
fun getDB(): TaskDatabase = TaskDatabase.getAppDatabase(context())
#Provides
#Singleton
fun context(): Context = app.applicationContext
#Provides
fun gettaskDao(taskDatabase: TaskDatabase) : TaskDao = taskDatabase.TaskDao()
}
My entity:
#Entity(tableName = "userinfo")
data class Task(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") val id: Int = 0,
#ColumnInfo(name = "name") val name: String,
#ColumnInfo(name = "email") val email: String,
#ColumnInfo(name = "phone") val phone: String?
)
My TaskDatabase as follows:
#Database(entities = [Task::class], version = 1)
abstract class TaskDatabase : RoomDatabase() {
abstract fun TaskDao(): TaskDao
companion object {
private var INSTANCE: TaskDatabase? = null
fun getAppDatabase(context: Context): TaskDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext, TaskDatabase::class.java, "AppDBB"
)
.allowMainThreadQueries()
.build()
}
return INSTANCE!!
}
}
}
My Dao interface.
#Dao
interface TaskDao {
#Query("SELECT * FROM userinfo")
fun getAllTaskInfo(): List<Task>?
#Insert
fun insertTask(user: Task?)
#Delete
fun deleteTask(user: Task?)
#Update
fun updateTask(user: Task?)
}
And now I have a logic to init my TaskViewModel inside my Fragment and attach observer to my Task List. However the app crashes.
Inside my fragment I have:
val viewModel: TaskViewModel by daggerViewModels { requireActivity() }
and also:
DaggerFragmentComponent
.builder()
.appComponent((requireActivity().application as App).getAppComponent())
.build()
.inject(this)
viewModel.allTaskList.observe(viewLifecycleOwner) {
// textView.text = it.toString()
}
and my TaskViewModel class is as follows:
class TaskViewModel #Inject constructor(var taskDao: TaskDao) : ViewModel() {
private var _allTaskList = MutableLiveData<List<Task>>()
val allTaskList = _allTaskList as LiveData<List<Task>>
init {
getAllRecords()
}
private fun getAllRecords() = _allTaskList.postValue(taskDao.getAllTaskInfo())
fun insertTask(task: Task) {
taskDao.insertTask(task)
getAllRecords()
}
}
Now I understand that this is A LOT of code, but can somebody help me figure this out? The dagger sees it's graph as I can build the project, so all the dependencies are provided. What I did wrong here? My logcat:
I found the solution myself. This was missing.
implementation 'androidx.room:room-runtime:2.5.0-alpha01'

A function is not executed in an unit test

I find this hard to explain but will try to keep it as simple as possible
I am writing a unit test in which a function (getQualityControlCheckDataForUplift()) is accessed in another class but it does get into the function and do what it is supposed to, it directly gets to return statement.
This is the function. should I be mocking the QualityControlCheck which is a data class
I have shortened the code to make it a bit clear and easier to follow
In my test case,
I load the MAP - qualityControlChecksDataProvider.loadMapWithQCChecksForMilestone()
Then orderStateMonitor access that map on calling orderStateMonitor.upliftedVolumeUpdated() in test case. which intern accesses getQualityControlCheckDataForUplift() via showQualityControlChecks()
cannot get into getQualityControlCheckDataForUplift() not sure why
any suggestions will be very helpful
I really apologise if the code is not simple to understand
fun getQualityControlCheckDataForUplift(qualityControlMilestone: QualityControlMilestone, uplift: Float):
ArrayList<QualityControlCheck>? {
val qcChecksForUplift: ArrayList<QualityControlCheck>? = ArrayList()
val qcChecksForMilestone = mapOfQCChecksForMilestone[qualityControlMilestone.milestoneText]
qcChecksForMilestone?.forEach {
if (it.uplift == uplift) qcChecksForUplift?.add(it)
}
return qcChecksForUplift
}
My test case
#RunWith(MockitoJUnitRunner::class)
class OrderStateMonitorTest : BaseTest() {
#InjectMocks
private lateinit var orderStateMonitor: OrderStateMonitor
#Mock
private lateinit var qualityControlCheckRequiredDuringFuellingBus: QualityControlCheckRequiredDuringFuellingBus
#Mock
private lateinit var qualityControlChecksDataProvider: QualityControlChecksDataProvider
#Before
fun setUp() {
qualityControlChecksDataProvider = QualityControlChecksDataProvider(offlineDataStorage, app)
}
#Test
fun `upliftedVolumeUpdated abcdefg`() {
qualityControlChecksDataProvider.loadMapWithQCChecksForMilestone()
every(fuelOrderValuesProvider.upliftedVolume()).thenReturn(1000.0f)
orderStateMonitor.upliftedVolumeUpdated()
verify(qualityControlCheckRequiredDuringFuellingBus, never()).postUnavailable()
verify(qualityControlCheckRequiredDuringFuellingBus, once()).postAvailable(qualityControlChecks)
}
}
OrderStateMonitor
class OrderStateMonitor #Inject constructor(
private val orderStorage: CurrentOrderStorage,
private val fuelOrderValuesProvider: FuelOrderValuesProvider,
private val noServiceRequiredAvailabilityBus: NoServiceRequiredAvailabilityBus,
private val qualityControlCheckRequiredDuringFuellingBus: QualityControlCheckRequiredDuringFuellingBus,
private val qualityControlChecksDataProvider: QualityControlChecksDataProvider
) {
fun upliftedVolumeUpdated() {
showQualityControlChecks()
}
private fun showQualityControlChecks() {
// WHEN I TRY TO ACCESS ` //qualityControlChecksDataProvider.getQualityControlCheckDataForUplift` IT DOES NOT GET INTO THAT FUNCTION
val qualityControlChecks =
qualityControlChecksDataProvider.getQualityControlCheckDataForUplift(
QualityControlMilestone.FUELLING, 1000.0f)
}
}
QualityControlChecksDataProvider
class QualityControlChecksDataProvider #Inject constructor(
private val offlineDataStorage: OfflineDataStorage,
private val app: App
) {
private val mapOfQCChecksForMilestone = LinkedHashMap<String, ArrayList<QualityControlCheck>?>()
fun loadMapWithQCChecksForMilestone() {
QualityControlMilestone.values().forEach {
mapOfQCChecksForMilestone.put(it.milestoneText, getQualityControlCheckDataForMilestone(it))
}
}
fun getQualityControlCheckData(qualityControlMilestone: QualityControlMilestone):
ArrayList<QualityControlCheck>? =
mapOfQCChecksForMilestone[qualityControlMilestone.milestoneText]
fun getQualityControlCheckDataForUplift(qualityControlMilestone: QualityControlMilestone, uplift: Float):
ArrayList<QualityControlCheck>? {
//DOES NOT GET INTO THIS FUNCTION I RUN THE TEST CASE
val qcChecksForUplift: ArrayList<QualityControlCheck>? = ArrayList()
val qcChecksForMilestone = mapOfQCChecksForMilestone[qualityControlMilestone.milestoneText]
qcChecksForMilestone?.forEach {
if (it.uplift == uplift) qcChecksForUplift?.add(it)
}
return qcChecksForUplift
}
//THERE ARE OTHER PRIVATE FUNCTION WHICH THE ABOVE CODE WILL ACCESS WHICH WORK FINE
}
The method not executed because you mocked the class
#RunWith(MockitoJUnitRunner::class)
class OrderStateMonitorTest : BaseTest() {
#InjectMocks
private lateinit var orderStateMonitor: OrderStateMonitor
#Mock
private lateinit var qualityControlCheckRequiredDuringFuellingBus: QualityControlCheckRequiredDuringFuellingBus
private lateinit var qualityControlChecksDataProvider: QualityControlChecksDataProvider
#Before
fun setUp() {
qualityControlChecksDataProvider = spy(QualityControlChecksDataProvider(offlineDataStorage, app))
}
#Test
fun `upliftedVolumeUpdated abcdefg`() {
qualityControlChecksDataProvider.loadMapWithQCChecksForMilestone()
every(fuelOrderValuesProvider.upliftedVolume()).thenReturn(1000.0f)
orderStateMonitor.upliftedVolumeUpdated()
verify(qualityControlCheckRequiredDuringFuellingBus, never()).postUnavailable()
verify(qualityControlCheckRequiredDuringFuellingBus, once()).postAvailable(qualityControlChecks)
}
}
or use CALL_REAL_METHOD
#RunWith(MockitoJUnitRunner::class)
class OrderStateMonitorTest : BaseTest() {
#InjectMocks
private lateinit var orderStateMonitor: OrderStateMonitor
#Mock
private lateinit var qualityControlCheckRequiredDuringFuellingBus: QualityControlCheckRequiredDuringFuellingBus
private lateinit var qualityControlChecksDataProvider: QualityControlChecksDataProvider
// java syntax
#before
public void setUp(){
qualityControlChecksDataProvider = mock(QualityControlChecksDataProvider, Mocktio.CALL_REAL_METHOD);
}
#Test
fun `upliftedVolumeUpdated abcdefg`() {
qualityControlChecksDataProvider.loadMapWithQCChecksForMilestone()
every(fuelOrderValuesProvider.upliftedVolume()).thenReturn(1000.0f)
orderStateMonitor.upliftedVolumeUpdated()
verify(qualityControlCheckRequiredDuringFuellingBus, never()).postUnavailable()
verify(qualityControlCheckRequiredDuringFuellingBus, once()).postAvailable(qualityControlChecks)
}
}

Categories