AS 4.0.2
RxBindings 4.0.0
I have written a unit test for testing if the PublishRelay accept has been called that uses jakeWartons rxBindings
This is the snippet of code I am testing
private val checkStoresRelay: Relay<Unit> = PublishRelay.create<Unit>()
private fun onCheckStockTapped(view: View) {
view.textViewCheckStock
.clicks()
.debounce(TIME_OUT, TimeUnit.MILLISECONDS)
.subscribeBy(
onNext = checkStoresRelay::accept,
onError = { Timber.e(it, it.localizedMessage) }
)
}
In the test class I am doing the following. The textViewCheckStock.performClick() will trigger and I check that the checkStoresRelay.accept(Unit) had been called.
But I am not sure about the availableDeliveryOptionsModel.checkStoresRelay.accept(Unit) As I don't think I should be calling this in the test. However, the test will fail if I remove this. Just wondering what would be the best way to test this?
#Test
fun `should check stores when checkStock is tapped`() {
// Arrange
val viewHolder = createViewHolder()
availableDeliveryOptionsModel.bind(viewHolder)
val actual = availableDeliveryOptionsModel.checkStoresRelay.test()
// Act
availableDeliveryOptionsModel.checkStoresRelay.accept(Unit)
viewHolder.itemView.textViewCheckStock.performClick()
// Assert
actual.assertValue(Unit)
}
Many thanks for any suggestions,
You definitely shouldn't call relay.accept() directly within your test. accept should be called on your behalf in subscribeBy(). The problem is based on .debounce() operator.
Debounce waits for a given amount of time and if there isn't subsequent emit (view click), it emits item downstream (into .subscribeBy()). This creates some delay and results in a test failure, because actual.assertValue(Unit) is called before click (emit) is delivered.
You can solve this issue with RxSchedulerRule. Execution of debounce operator is done immediately on the current thread, so you'll basically handle item delivery to relay and run assertions after.
I have simplified your example a little bit, but I hope the main point remains:
class SO64328289 {
val checkStoresRelay: Relay<Unit> = PublishRelay.create()
fun onCheckStockTapped(view: View) {
view.clicks()
.debounce(1, TimeUnit.MILLISECONDS)
.subscribeBy(
onNext = checkStoresRelay::accept,
onError = { Log.e("SO64328289", it.localizedMessage) }
)
}
}
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [28])
class SO64328289Test {
#get:Rule val schedulerRule = RxSchedulerRule()
#Test
fun `when button clicked then relay emits value`() {
val tested = SO64328289()
val view = View(ApplicationProvider.getApplicationContext())
tested.onCheckStockTapped(view)
val relayTestSubscription = tested.checkStoresRelay.test()
view.performClick()
relayTestSubscription.assertValue(Unit)
}
}
I have used Robolectric, but it should also work with other testing frameworks. These are dependencies I have used in my test project if you want to try it for yourself:
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
implementation "io.reactivex.rxjava2:rxjava:2.2.20"
testImplementation 'com.github.Plastix.RxSchedulerRule:rx2:1.0.2' // requires jitpack.io maven
testImplementation 'org.robolectric:robolectric:4.4'
testImplementation 'junit:junit:4.13.1'
testImplementation 'androidx.test:core:1.3.0'
Related
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))
}
}
I'm trying to use livedata builder functions. Indeed, it's so easy in use, but actually I can't understand how I can restart my coroutine. Below my part of code:
val topStoriesResult : LiveData<UIState<TopStoryWrapper>> = liveData(Dispatchers.IO) {
topStoriesRepository.getTopStoriesSetWrapper().apply {
emit(UIState.Loading)
onFailure { emit(UIState.NoData) }
onSuccess { emit(UIState.HasData(it)) }
}
}
liveData builder can't be restarted, docs say:
The liveData building block serves as a structured concurrency primitive between coroutines and LiveData. The code block starts executing when LiveData becomes active and is automatically canceled after a configurable timeout when the LiveData becomes inactive. If it is canceled before completion, it is restarted if the LiveData becomes active again. If it completed successfully in a previous run, it doesn't restart. Note that it is restarted only if canceled automatically. If the block is canceled for any other reason (e.g. throwing a CancelationException), it is not restarted.
To make code run a couple of times I can suggest to create a function and call it when you need, e.g. on button click:
class MainViewModel : ViewModel() {
val topStoriesResult: LiveData<UIState<TopStoryWrapper>> = MutableLiveData<UIState<TopStoryWrapper>>()
fun loadTopStories() = viewModelScope.launch(Dispatchers.IO) { // start a coroutine
topStoriesRepository.getTopStoriesSetWrapper().apply {
val mutableLiveData = loginResponse as MutableLiveData
// post value to LiveData
mutableLiveData.postValue(UIState.Loading)
onFailure { mutableLiveData.postValue(UIState.NoData) }
onSuccess { mutableLiveData.postValue(UIState.HasData(it)) }
}
}
}
To use viewModelScope in MainViewModel class add dependency to build.gradle file:
final LIFECYCLE_VERSION = "2.2.0-rc03" // add most recent version
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
I'm testing a view model which has the following definition:
class PostViewModel(private val postApi: PostApi): ViewModel() {
private val _post: PublishSubject<Post> = PublishSubject.create()
val postAuthor: Observable<String> = _post.map { it.author }
fun refresh(): Completable {
return postApi.getPost() // returns Single<Post>
.doOnSuccess {
_post.onNext(it)
}
.ignoreElement()
}
}
}
My fragment then displays the post author by subscribing to viewModel.postAuthor in its onActivityCreated and calling and subscribing to refresh() whenever the user wants an updated post and everything is fine and dandy.
The issue I'm running into is trying to verify this behaviour in a unit test: specifically, I am unable to get postAuthor to emit an event in my testing environment.
My test is defined as follows:
#Test
fun `When view model is successfully refreshed, display postAuthor`() {
val post = Post(...)
whenever(mockPostApi.getPost().thenReturn(Single.just(post))
viewModel.refresh()
.andThen(viewModel.postAuthor)
.test()
.assertValue { it == "George Orwell" }
}
The test fails due to no values or errors being emitted, even though I can verify through the debugger that the mock does in-fact return the Post as expected. Is there something obvious that I'm missing, or am I completely wrong in my testing approach?
viewModel.postAuthor is a hot-observable. It emits value when you call _post.onNext(it).
Unlike a cold-observable, the late subscribers cannot receive the values that got emitted before they subscribe.
So in your case I think the viewModel.postAuthor is subscribed after you call viewModel.refresh(), so it cannot receive the value.
The observable could be emitting on a different thread so that's why it's empty when the test is checking the values/errors.
You could try forcing your observable to emit on the same thread. Depending on which scheduler you're using, it'd be something like:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
I'm trying to test this code with mockito.
public class Repository {
...
#Override
public Observable<Data> getCurrentData() {
return api.getData()
.map(entityMapper::transform);
}
}
And I would like to test the entityMapper interaction. Here my test scenery:
#Test
#Throws(Exception::class)
fun getData() {
//given
whenever(api.getData).thenReturn(Observable.just(Data()))
//when
debitCardRepo.getCurrentData
//then
verify(api).getData
//TODO verify entityMapper interaction
}
If I try verify(entityMapper).transform(anyOrNull<>()), I will get Wanted but not invoked:
Does anyone knows how to test an mock interaction inside a map/flapmap?
Does anyone knows how to test an mock interaction inside a map/flapmap?
Assuming the rest of your class looks like this:
public class Repository {
private final EntityMapper
public Repository(EntityMapper entityMapper) {
this.entityMapper = entityMapper;
}
#Override
public Observable<Data> getCurrentData() {
return api.getData()
.map(entityMapper::transform);
}
}
Then stubbing a behaviour on a mocked EntityMapper will work if you wait for the Observable to complete:
#Test
#Throws(Exception::class)
fun testGetData() {
//given
val data = Data()
whenever(api.getData).thenReturn(Observable.just(data))
//when
repository.getCurrentData().blockingGet()
//then
verify(entityMapper).transform(any())
}
Note the call to blockingGet() - otherwise it is possible for the test to complete before the mapping has occurred. You should also look at the Observable#test() method to see better options here.
However in this case since the repository does very little apart from delegating to the API and calling the EntityMapper and this seems to be the main interaction you are interested in, why not test EntityMapper separately?
If you write a separate test for EntityMapper then you can use a black-box test (simply call transform on your data and see if the transformation matches your expectations). This kind of test is much more stable and valuable than white-box testing with verify which can sometimes degenerate into tests that are a reverse implementation of system under test.
You didn't subscribe.
debitCardRepo.getCurrentData will just return an Observable but not actually really do anything.
#Test
#Throws(Exception::class)
fun getData() {
//given
whenever(api.getData).thenReturn(Observable.just(Data()))
//when
debitCardRepo.getCurrentData.subscribe()
//then
verify(api).getData
}
regardless, this isn't a great test, as you're testing a side effect happens. some map/transform function is called. Why don't you test the output?
#Test
#Throws(Exception::class)
fun getData() {
//given
val data = Data()
whenever(api.getData).thenReturn(Observable.just(data))
//when
val transformedData = debitCardRepo.getCurrentData.blockingGet()
//then
assertEquals(data, transformedData)
}
This is a more meaningful test. Simple refactors won't break this test unless the change the behavior of the class.
I am testing Kotlin coroutines in my Android app and I am trying to do the following unit test
#Test fun `When getVenues success calls explore venues net controller and forwards result to listener`() =
runBlocking {
val near = "Barcelona"
val result = buildMockVenues()
val producerJob = produce<List<VenueModel>>(coroutineContext) { result.value }
whenever(venuesRepository.getVenues(eq(near))) doReturn producerJob // produce corooutine called inside interactor.getVenues(..)
interactor.getVenues(near, success, error) // call to real method
verify(venuesRepository).getVenues(eq(near))
verify(success).invoke(argThat {
value == result.value
})
}
The interactor method is as follows
fun getVenues(near: String, success: Callback<GetVenuesResult>,
error: Callback<GetVenuesResult>) =
postExecute {
repository.getVenues(near).consumeEach { venues ->
if (venues.isEmpty()) {
error(GetVenuesResult(venues, Throwable("No venues where found")))
} else {
success(GetVenuesResult(venues))
}
}
}
postExecute{..} is a method on a BaseInteractor that executes the function in the ui thread through a custom Executor that uses the launch(UI) coroutine from kotlin android coroutines library
fun <T> postExecute(uiFun: suspend () -> T) =
executor.ui(uiFun)
Then the repository.getVenues(..) function is also a coroutine that returns the ProducerJob using produce(CommonPool) {}
The problem is that it seams that success callback in the interactor function doesn't seem to be executed as per the
verify(success).invoke(argThat {
value == result.value
})
However, I do see while debugging that the execution in the interactor function reaches to the if (venues.isEmpty()) line inside the consumeEach but then from there exits and continues with the test, obviously failing on the verify for the success callback.
I am a bit new on coroutines so any help would be appreciated.
I figured this one out. I saw that the problem was just with this producing coroutine and not with the others tests that are also using coroutines and working just fine. I noticed that I actually missed the send on the mocked ProducingJob in order to have it actually produce a value, in this case the list of mocks. I just added that changing the mock of the producing job to
val producerJob = produce { send(result.value) }