ViewModel Junit Test fails in case when i use Junit5 and Rxjava. Method getMainLooper in android.os.Looper not mocked - rx-java2

I am trying to wrote test case for my view model and I am using junit5 along with mockito. The issue is my test case is always failing it gives me this error
at android.os.Looper.getMainLooper(Looper.java)
at io.reactivex.android.schedulers.AndroidSchedulers$MainHolder.<clinit>
Now I did a lot of research on this issue and found out that this is due to Rxjava scheduler as it is not able to run on the background thread I followed this link and did the required step so after that my view model looks like this
MainViewModelTest.kt
#ExtendWith(value = [InstantExecutorExtension::class,TestSchedulerExtension::class])
class MainViewModelTest {
#Mock
private lateinit var repository: GithubRepository
private lateinit var viewModel: MainViewModel
#Before
fun init() {
MockitoAnnotations.initMocks(this)
viewModel = MainViewModel(repository)
}
#Test
fun testNull(){
assertThat(viewModel.observeTrendingRepository(), notNullValue())
verify(repository, never()).makeRequestForTrendingRepo(anyBoolean())
}
#Test
fun fetchTrendingGitHubRepository_emptyDbFirstTimeCase(){
`when`(repository.makeRequestForTrendingRepo(anyBoolean()))
.thenReturn(Flowable.just(Resource.Loading(listOf())))
viewModel.fetchTrendingGitHubRepository(false) // this is where it fails
assertEquals(Resource.Loading<List<Repository>>(listOf()),LiveDataTestUtil.getValue(viewModel.observeTrendingRepository()));
}
}
Here are the InstantExecutorExtension and TestSchedulerExtension that take care of live data and Rxjava respectively.
package com.rajat.zomatotest.utils
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) {
runnable.run()
}
override fun postToMainThread(runnable: Runnable) {
runnable.run()
}
override fun isMainThread(): Boolean {
return true
}
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
package com.rajat.zomatotest.utils
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import org.junit.jupiter.api.extension.AfterTestExecutionCallback
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback
import org.junit.jupiter.api.extension.ExtensionContext
class TestSchedulerExtension : BeforeTestExecutionCallback, AfterTestExecutionCallback {
override fun beforeTestExecution(context: ExtensionContext?) {
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }
}
override fun afterTestExecution(context: ExtensionContext?) {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
I couldn't understand why is it giving me error, I have correctly added TestSchedulerExtension.kt so this should work!!

I run into the same problem, and I found that the way to fix this was by using
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
instead of
RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler }
In this link you will find more information.

Related

#DynamicPropertySource not being invoked (Kotlin, Spring Boot and TestContainers)

I'm trying to define a #TestConfiguration class that is executed once before all integration tests to run a MongoDB TestContainer in Kotlin in a Spring Boot project.
Here is the code:
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.MongoDBContainer
import org.testcontainers.utility.DockerImageName
#TestConfiguration
class TestContainerMongoConfig {
companion object {
#JvmStatic
private val MONGO_CONTAINER: MongoDBContainer = MongoDBContainer(DockerImageName.parse("mongo").withTag("latest")).withReuse(true)
#JvmStatic
#DynamicPropertySource
private fun emulatorProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.data.mongodb.uri", MONGO_CONTAINER::getReplicaSetUrl)
}
init { MONGO_CONTAINER.start() }
}
}
The issue seems to be that emulatorProperties method is not being called.
The regular flow should be that the container is started and then the properties are set.
The first step happens, the second does not.
I know there is an alternative for which I can do this configuration in each functional test class but I don't like it as it adds not needed noise to the test class.
For example, with a Java project that uses Postgres I managed to make it work with the following code:
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
#TestConfiguration
public class PostgresqlTestContainersConfig {
static final PostgreSQLContainer POSTGRES_CONTAINER;
private final static DockerImageName IMAGE = DockerImageName.parse("postgres").withTag("latest");
static {
POSTGRES_CONTAINER = new PostgreSQLContainer(IMAGE);
POSTGRES_CONTAINER.start();
}
#Bean
DataSource dataSource() {
return DataSourceBuilder.create()
.username(POSTGRES_CONTAINER.getUsername())
.password(POSTGRES_CONTAINER.getPassword())
.driverClassName(POSTGRES_CONTAINER.getDriverClassName())
.url(POSTGRES_CONTAINER.getJdbcUrl())
.build();
}
}
I'm trying to achieve the same thing but in Kotlin and using MongoDB.
Any idea on what may be the issue causing the #DynamicPropertySource not being called?
#DynamicPropertySource is part of the Spring-Boot context lifecycle. Since you want to replicate the Java setup in a way, it is not required to use #DynamicPropertySource. Instead you can follow the Singleton Container Pattern, and replicate it in Kotlin as well.
Instead of setting the config on the registry, you can set them as a System property and Spring Autoconfig will pick it up:
init {
MONGO_CONTAINER.start()
System.setProperty("spring.data.mongodb.uri", MONGO_CONTAINER.getReplicaSetUrl());
}
I was able to resolve similar problem in Groovy by:
Having static method annotated with #DynamicPropetySource directly in the test class (probably it would also work in superclass.
But I didn't want to copy the code into every test class that needs MongoDB.
I resolved the issue by using ApplicationContexInitializer
The example is written in groovy
class MongoTestContainer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6.0.2"))
#Override
void initialize(ConfigurableApplicationContext applicationContext) {
mongoDBContainer.start()
def testValues = TestPropertyValues.of("spring.data.mongodb.uri="+ mongoDBContainer.getReplicaSetUrl())
testValues.applyTo(applicationContext.getEnvironment())
}
}
To make it complete, in the test class, you just need to add #ContextConfiguration(initializers = MongoTestContainer) to activate context initializer for the test.
For this you could also create custom annotation which would combine #DataMongoTest with previous annotation.
This solution works for me.
Method with #DynamicPropertySource is inside companion object(also added #JvmStatic) and added org.testcontainers.junit.jupiter.Testcontainers on the test class
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import javax.sql.DataSource
#ExtendWith(SpringExtension::class)
#Testcontainers
#TestConfiguration
#ContextConfiguration(classes = [PostgresqlTestContainersConfig::class])
class PostgresqlTestContainersConfig {
#Autowired
var dataSource: DataSource? = null
#Test
internal fun name() {
dataSource!!.connection.close()
}
#Bean
fun dataSource(): DataSource? {
return DataSourceBuilder.create()
.username(POSTGRES_CONTAINER.getUsername())
.password(POSTGRES_CONTAINER.getPassword())
.driverClassName(POSTGRES_CONTAINER.getDriverClassName())
.url(POSTGRES_CONTAINER.getJdbcUrl())
.build()
}
companion object {
#JvmStatic
#Container
private val POSTGRES_CONTAINER: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:9.6.12")
.withDatabaseName("integration-tests-db")
.withUsername("sa")
.withPassword("sa")
#JvmStatic
#DynamicPropertySource
fun postgreSQLProperties(registry: DynamicPropertyRegistry) {
registry.add("db.url") { POSTGRES_CONTAINER.jdbcUrl }
registry.add("db.user") { POSTGRES_CONTAINER.username }
registry.add("db.password") { POSTGRES_CONTAINER.password }
}
}
}

Reactive repository does not save an object

I think I don't understand well how a Reactive repository and handlers using it work. I have written a special test class only to test the simpliest handler using a repository
#SpringBootTest
class TestRepository() {
#Autowired
lateinit var myRepo: myRepo
#Autowired
lateinit var myHandler: MyHandler
#Test
fun `save with a handler`() {
val myObject = MyObject()
myHandler.save(request).subscribe()
StepVerifier.create (myRepository.count() ) <--this does not work
.expectNext (1L )
.expectComplete().verify()
}
#Test
fun `test only database saving`() {
val object = MyObject()
myRepo.save(myRepo).subscribe()
StepVerifier.create (myRepo.count() ) <-- this works
.expectNext (1L )
.expectComplete().verify()
}
}
my handler and repository are defined in the following way:
#Service
class MyHandler(private val myRepository: MyRepository) {
fun save(object: MyObject): Mono<MyObject> {
return myRepository.save(request)
}
}
#Repository
interface MyRepo : ReactiveMongoRepository<MyObject, String> {
fun save(request: MyObject): Mono<MyObject>
}
I also tried to play with subscribe method but it still does not see the results.
What should I correct?
Use Mono.then function to chain save and count functions and get a resulting Mono:
#Test
fun `save with a handler`() {
val countAfterSave = myHandler.save(MyObject()).then(myRepository.count());
StepVerifier.create(countAfterSave)
.expectNext(1L)
.expectComplete()
.verify()
}

Dagger can not inject ViewModel with KClass

I am moving my project java to kotlin, but got some confusion about KClass and Class
Here is my BaseActivity
abstract class BaseActivity<DB : ViewDataBinding, VM : BaseViewModel> : DaggerAppCompatActivity() {
private lateinit var mCustomDialog: CustomDialog
private lateinit var mViewDataBinding: DB
private lateinit var mViewModel : VM
#Inject
lateinit var viewModelFactory: ViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set Custom Dialog
mCustomDialog = CustomDialog(this, R.style.LoadingDialogStyle)
// Set ViewModel
mViewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
// Set DataBinding
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId())
mViewDataBinding.lifecycleOwner = this
mViewDataBinding.setVariable(getBindingVariable(), mViewModel)
mViewDataBinding.executePendingBindings()
// Initialize UI
prepareView(savedInstanceState)
}
#LayoutRes
abstract fun getLayoutId(): Int
protected abstract fun getViewModelClass(): KClass<VM>
abstract fun getBindingVariable(): Int
fun getViewModel(): VM {
return mViewModel
}
fun getViewDataBinding() : DB {
return mViewDataBinding
}
I am using protected abstract fun getViewModelClass(): KClass<VM> function for initializing ViewModel class in the function below
ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
I use ViewModel in activities by this way
class SplashActivity : BaseActivity<ActivitySplashBinding, SplashViewModel>() {
override fun getViewModelClass(): KClass<SplashViewModel> {
return SplashViewModel::class
}
override fun getLayoutId(): Int {
return R.layout.activity_splash
}
override fun getBindingVariable(): Int {
return BR.vm
}
override fun prepareView(savedInstanceState: Bundle?) {
getViewModel().testLog()
}
}
But when I run the project, I got this error
error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.example.example.MyApp> {
^
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.example.example.utils.ViewModelFactory(viewModels)
com.example.example.utils.ViewModelFactory is injected at
com.example.example.base.BaseActivity.viewModelFactory
com.example.example.ui.splash.SplashActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.example.example.di.AppComponent ? com.example.example.di.ActivityBindingsModule_SplashActivityInjector$app_debug.SplashActivitySubcomponent]
So I made some research and find out it is about KClass in my ViewModelKey
Here is ViewModelKey
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
If I do not change my code to Kotlin and use old Java class like this it works properly
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
public #interface ViewModelKey {
Class<? extends ViewModel> value();
}
This is my ViewModelFactory class
#Suppress("UNCHECKED_CAST")
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 {
val creator = viewModels[modelClass]
?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
My SplashActivityModule
#Module
abstract class SplashActivityModule {
#Binds
#IntoMap
#ViewModelKey(SplashViewModel::class)
internal abstract fun provideSplashViewModel(splashViewModel: SplashViewModel) : ViewModel
}
So how can I use ViewModelKey properly with Kotlin and what is main cause of this error, any help will be appreciated
Your ViewModelKey be like
#MustBeDocumented
#kotlin.annotation.Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
As mentioned this question problem is related to Kotlin version. Using higher than 1.3.30 version solves the problem.

Cannot access 'TAG': it is invisible (private in supertype) in 'AppCompatActivity'

I am a beginner in android programming. I'm currently using android studio 3.2.1.
I am trying to monitor the different states of an android activity in log. I have written the code shown below, but I keep receiving the error message:
Cannot access 'TAG': it is invisible (private in supertype) in 'AppCompatActivity'.
Even after searching, I cannot figure the error. Can some one help?
Code:
package com.cooperation.bestech.test1
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log;
class MainActivity : AppCompatActivity() {
private static final String TAG = "MyMessage";
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i(TAG, "onCreate");
}
override fun onStart() {
super.onStart()
Log.i(TAG, "onStart");
}
override fun onPause() {
super.onPause()
Log.i(TAG, "onPause");
}
override fun onResume() {
super.onResume()
Log.i(TAG, "onResume");
}
override fun onStop() {
super.onStop()
Log.i(TAG, "onStop");
}
override fun onRestart() {
super.onRestart()
Log.i(TAG, "onRestart");
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "onDestroy");
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
Log.i(TAG, "onSaveInstanceState");
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
Log.i(TAG, "onRestoreInstanceState");
}
}
Since the original question is written in Kotlin (from comment), ignore the request of "launch Java project instead of the default Kotlin" may be a better solution.
And, applying the correct way to declare and assign a variable, your code segment should be like this:
class MainActivity : AppCompatActivity() {
val TAG = "MyMessage"
import android.content.ContentValues.TAG
Seems you are using Kotlin.So you need to define TAG in kotlin way:
In Kotlin constants located in the companion object:
class MyClass {
companion object {
private val TAG = "ClassName"
}
}

How to access class properties in Unit Test

I have a class:
public class Foo {
public var options = "bar"
public func someMethod() {
//...
}
}
When I access this options property in a test, the property name cannot be resolved, but I can still access the property from an instance outside of a test.
What did I do wrong?
options should be testable. Make sure you have a #testable import at the top for your app target in the unit tests:
import XCTest
#testable import UnitTest
class UnitTestTests: XCTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
func testFoo() {
let foo = Foo()
XCTAssert(foo.options == "bar")
}
}
In my Xcode this test passes:
If you're still seeing an issue, try cleaning the build folder.