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)
}
}
Related
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.
}
}
I want to init and call a kotlin viewModel from a java class.
this is my viewModel
#HiltViewModel
class PermProdsTestViewModel #Inject constructor(
private val prodsUseCase: ProductUseCase
) : ViewModel() {
private val _prods = MutableStateFlow(ProdsState())
val prods: StateFlow<ProdsState> = _prods
fun getPermittedProducts(serviceName: String?, productTypes: List<String>?, permission: String?, subServiceName: String?, filter: Boolean?) =
viewModelScope.launch(Dispatchers.IO) {
permittedProdsUseCase.invoke(serviceName, productTypes, permission, subServiceName, filter).collect() {
when (it) {
is DataResult.Success -> {
_prods.value = ProdsState(products = it.data)
Timber.d("Api request success, getting results")
}
is DataResult.Error -> {
ProdsState(error = it.cause.localizedMessage ?: "Unexpected Error")
Timber.d("Error getting permitted products")
}
}
}
}}
and I want to call it from a java file activity and use the method.
How can i do it?
Did you maybe forgot to put annotation #AndroidEntryPoint above your class declaration? I tried to access to your ViewModel class from java class and it works for me.
Here is my code:
#AndroidEntryPoint
public class TestActivity extends AppCompatActivity {
private PermProdsTestViewModel vm;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
vm = new ViewModelProvider(this).get(PermProdsTestViewModel.class);
vm.getPermittedProducts();
}
}
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()
}
}
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'
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)
}
}