I'm trying to use MockWebServer in my instrumented tests but its not working. The real API is being called, and not the mock itself.
com.squareup.okhttp3:mockwebserver:4.10.0
I have my base class:
abstract class BaseInstrumentedTest {
private var mockWebServer: MockWebServer = MockWebServer()
init {
mockWebServer.start(8080)
}
#After
fun after() {
mockWebServer.shutdown()
}
fun setDispatcher(it: Dispatcher){
mockWebServer.dispatcher = it
}
}
And I'm using this dispatcher just to see how it works.
fun getDispatcher(): Dispatcher {
return object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
return MockResponse().setResponseCode(500)
}
}
}
So, I could do it like:
#Test
fun someTestHere() {
setDispatcher(getDispatcher())
launchActivity()
}
But unfortunately its not working.
Any guesses?
Related
I created an instrumental test for a use case which has two coroutines, before fetching data and after. I run a test on the usecase but the test passes before the suspend function is done.
Usecase:
class FetchAllUseCase : Observable<FetchAllUseCase.Listener>() {
interface Listener {
fun onFetched(obj: List<Obj>)
}
fun fetchObjs() {
GlobalScope.launch(Dispatchers.IO) {
withContext(NonCancellable) {
val objs: List<Obj> = getObjs()
notify(objs)
}
}
}
private suspend fun notify(objs: List<Objs>) {
withContext(Dispatchers.Main) {
for(listener in listeners){
listener.onFetched(objs)
}
}
}
private fun getObjss(): List<Obj> {
/// fetch objects functionality
}
Test:
class FetchAllUseCase Test: FetchAllUseCase .Listener {
private lateinit var fetchAllUseCase: FetchAllUseCase
#Before
fun setUp() {
fetchAllUseCase = FetchAllUseCase()
fetchAllUseCase.registerListener(this)
}
#After
fun tearDown() {
fetchAllUseCase.unregisterListener(this)
}
#ExperimentalCoroutinesApi
#Test
fun fetchObjss()= runBlocking{
fetchAllUseCase.fetchObjs()
}
override fun onFetched(objs: List<Objs>) {
fail() //does not fail the test
}
}
Test already passes before onFetched() is called.
fail() or any other assertEquals that should fail the test does not affect the test result.
If I run the test on the MainActivity , adding:
#Rule
#JvmField
val mMainActivityTestRule = ActivityTestRule(MainActivity::class.java)
I will get a message "Process crashed" , but the test already passed.
If I remove both coroutines, the test works as expected, i.e fails.
I tried runBlocking, runBlockingTest, TestCoroutineDispatcher
None solved the issue so far
Add Dispatchers as Dependencies
class FetchAllUseCase(
private val mainDispatcher : CoroutineDispatcher = Dispatchers.Main,
private val ioDispatcher : CoroutineDispatcher = Dispatchers.IO,
) : Observable<FetchAllUseCase.Listener>() {
interface Listener {
fun onFetched(obj: List<Obj>)
}
fun fetchObjs() {
GlobalScope.launch(ioDispatcher) {
withContext(NonCancellable) {
val objs: List<Obj> = getObjs()
notify(objs)
}
}
}
private suspend fun notify(objs: List<Objs>) {
withContext(mainDispatcher) {
for(listener in listeners){
listener.onFetched(objs)
}
}
}
private fun getObjss(): List<Obj> {
/// fetch objects functionality
}
Testing, Inject The TestCoroutineDispatcher and use runBlockingTest
class FetchAllUseCase Test: FetchAllUseCase .Listener {
private lateinit var fetchAllUseCase: FetchAllUseCase
private val testDispatcher = TestCoroutineDispatcher()
#Before
fun setUp() {
fetchAllUseCase = FetchAllUseCase(testDispatcher,testDispatcher)
fetchAllUseCase.registerListener(this)
}
#After
fun tearDown() {
fetchAllUseCase.unregisterListener(this)
}
#ExperimentalCoroutinesApi
#Test
fun fetchObjss()= runBlockingTest{
fetchAllUseCase.fetchObjs()
}
override fun onFetched(objs: List<Objs>) {
fail() //does not fail the test
}
If This Didn't work checkout this link Kotlin Coroutines in Android — Unit Test
I'm working on Android for a while but it's the first time I have to write some unit tests.
I have a design pattern in MVP so basically I have my Presenter, which have a contract (view) and it's full in kotlin, using coroutines.
Here is my Presenter class : The Repository and SomeOtherRepository are kotlin object so it's calling methods directly (The idea is to not change the way it's working actually)
class Presenter(private val contractView: ContractView) : CoroutinePresenter() {
fun someMethod(param1: Obj1, param2: Obj2) {
launch {
try {
withContext(Dispatchers.IO) {
val data = SomeService.getData() ?: run { throw Exception(ERROR) } // getData() is a suspend function
Repository.doRequest(param1, param2) // doRequest() is a suspend function also
}.let { data ->
if (data == null) {
contractView.onError(ERROR)
} else {
if (SomeOtherRepository.validate(data)) {
contractView.onSuccess()
} else {
contractView.onError(ERROR)
}
}
} catch (exception: Exception) {
contractView.onError(exception)
}
}
}
}
So the goal for me is to create unit test for this Presenter class so I created the following class in order to test the Presenter. Here is the Test implementation :
I read a lot of articles and stackoverflow links but still have a problem.
I setup a TestCoroutineRule which is like this :
#ExperimentalCoroutinesApi
class TestCoroutineRule(
private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
private fun TestCoroutineRule.runBlockingTest(block: suspend () -> Unit) =
testDispatcher.runBlockingTest { block() }
}
And here is the PresenterTest implementation :
#ExperimentalCoroutinesApi
class PresenterTest {
#get:Rule
val testCoroutineRule = TestCoroutineRule()
#Mock
private lateinit var view: ContractView
#Mock
private lateinit var repository: Repository
private lateinit var presenter: Presenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
presenter = Presenter(view)
}
#Test
fun `test success`() =
testCoroutineRule.runBlockingTest {
// Given
val data = DummyData("test", 0L)
// When
Mockito.`when`(repository.doRequest(param1, param2)).thenReturn(data)
// Then
presenter.someMethod("test", "test")
// Assert / Verify
Mockito.verify(view, Mockito.times(1)).onSuccess()
}
}
The problem I have is the following error Wanted but not invoked: view.onSuccess(); Actually there were zero interactions with this mock.
The ContractView is implemented in the Activity so I was wondering if I have to use Robolectric in order to trigger the onSuccess() method within the Activity context. I also think that I have a problem regarding the usage of coroutines maybe. I tried a lot of things but I always got this error on the onSuccess et onError view, if anyone could help, would be really appreciated :)
There could be other problems, but at a minimum you are missing:
Mockito.`when`(someOtherRepository.validate(data)).thenReturn(data)
Mockito.`when`(someService.getData()).thenReturn(data)
Use your debugger and check your logs to inspect what the test is doing
I am testing a suspended method from my ViewModel that triggers LiveData to emit an object when coroutine is completed. When
I run each of those tests individually they pass, when I run them together always the first test fails. Surprisingly, when I run them in debug and I put break points at assertValue to check what the vaule is, both of the test pass. My guess is that the problem is with the state of LiveData or the whole PaymentViewModel. What am I doing wrong?
class PaymentViewModelTest : KoinTest {
private val paymentViewModel : PaymentViewModel by inject()
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
#Before
fun setup(){
Dispatchers.setMain(mainThreadSurrogate)
val modules = KoinModule()
startKoin {
androidContext(mock(Application::class.java))
modules(listOf(
modules.repositoryModule,
modules.businessModule,
modules.utilsModule)
)
}
declareMock<AnalyticsHelper>()
declareMock<Printer>()
}
#After
fun after(){
stopKoin()
Dispatchers.resetMain()
}
#Test
fun successfully_initializes_payment_flow() {
declareMock<PaymentRepository> {
runBlockingTest {
given(initPayment())
.willAnswer { InitPaymentResponse(0, PaymentStatus.INITIALIZED, 0) }
}
}
paymentViewModel.initPayment(BigDecimal(0))
paymentViewModel.paymentStatus.test()
.awaitValue()
.assertValue { value -> value.getContentIfNotHandled()?.data == PaymentStatus.INITIALIZED }
}
#Test
fun fails_to_initialize_payment_flow() {
declareMock<PaymentRepository> {
runBlockingTest {
given(initPayment())
.willThrow(MockitoKotlinException("", ConnectException()))
}
}
paymentViewModel.initPayment(BigDecimal(0))
paymentViewModel.paymentStatus.test()
.awaitValue()
.assertValue { value -> value.getContentIfNotHandled()?.status == ApiResponseStatus.ERROR}
}
}
Here is the method that I am testing:
fun initPayment(price: BigDecimal) {
paymentStatus.postValue(Event(ApiResponse.loading()))
viewModelScope.launch {
runCatching {
repository.initPayment()
}.onSuccess {
paymentSession = PaymentSession(it.paymentId)
paymentSession.price = price
postPaymentStatus(it.status)
}.onFailure {
postApiError(it)
}
}
}
private fun postPaymentStatus(status: PaymentStatus) =
paymentStatus.postValue(Event(ApiResponse.success(status)))
This might not be a complete answer because there is so much in your question. Start by trying to use a CoroutineTestRule:
#ExperimentalCoroutinesApi
class CoroutineTestRule(
private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher() {
override fun starting(description: Description?) {
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
Your test will be something like:
class PaymentViewModelTest : KoinTest {
private val paymentViewModel : PaymentViewModel by inject()
#get:Rule
val coroutineTestRule = CoroutineTestRule()
#Before
fun setup(){
startKoin {
androidContext(mock(Application::class.java))
modules(
modules.repositoryModule,
modules.businessModule,
modules.utilsModule
)
}
declareMock<AnalyticsHelper>()
declareMock<Printer>()
}
#After
fun after(){
stopKoin()
}
// Other methods are the same.
}
You can use an AutoCloseKoinTest to remove that after() method.
You say that the test is passing when you run it isolated, so maybe this is enough. But there is more to dig into if this doesn't work. For example, I find it strange that you use runBlockingTest inside a mock and the assert is outside that block. Usually I would use MockK to mock suspending functions and test and assert any of them inside a runBlockingTest.
Rxjava 2 on error is not called while unit testing . I am using kotlin with rxJava 2 .
method
#SuppressLint("CheckResult")
override fun fetchFilms() {
view.showLoader(true)
disposable = getRetrofitInstance(false).create(API::class.java)
.fetchFilms()
.subscribeOn(io)
.observeOn(main)
.subscribeWith(object : DisposableObserver<Response>() {
override fun onComplete() {
}
override fun onNext(t: Response) {
println(t)
view.setAdapter(t?.results as List<ResultsItem>)
view.showLoader(false)
}
override fun onError(e: Throwable) {
e.printStackTrace()
view.showLoader(false)
view.showError(e.message.toString()) }
})
}
Test
#Test
fun fetchInvalidDataShouldThrowError(){
`when`(api.fetchFilms()).thenReturn(Observable.error(IOException()))
filmsPresenter.fetchFilms()
val subscriber = TestSubscriber<Response>()
subscriber.awaitTerminalEvent(5,TimeUnit.SECONDS)
verify(filmsView, times(1)).showLoader(true)
verify(filmsView).showError("t")
verify(filmsView).showLoader(false)
}
It seems like you're running your code asynchonously in tests as well.
You'd have to synchronise the schedulers you use before.
#Before
fun setupSchedulers() {
RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
}
#Test
fun fetchInvalidDataShouldThrowError() {
`when`(api.fetchFilms()).thenReturn(Observable.error(IOException("t")))
filmsPresenter.fetchFilms()
verify(filmsView).showLoader(true)
verify(filmsView).showError("t")
verify(filmsView).showLoader(false)
}
I had pass repo to Presenter to have same instance API .
here are some changes i have to make
class RepoTest(val api : API) {
fun filmm() : Observable<Response> {
return api.fetchFilms()
}
}
presenter
class FlimsPresenter(private val view: GetFlimsContract.View, repo: RepoTest) : GetFlimsContract.Presenter
method
override fun fetchFilms() {
view.showLoader(true)
disposable = repot.filmm()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<Response>() {
override fun onComplete() {
}
override fun onNext(t: Response) {
println(t)
view.setAdapter(t?.results as List<ResultsItem>)
if (t?.results.isEmpty()) view.showEmptyView(true)
else view.showEmptyView(false)
view.showLoader(false)
}
override fun onError(e: Throwable) {
e.printStackTrace()
view.showLoader(false)
view.showError(e.message.toString())
}
})
}
Finally test
class TestSample {
#Mock
private lateinit var filmsPresenter: GetFlimsContract.Presenter
#Mock
private lateinit var filmsView: GetFlimsContract.View
private lateinit var api: API
#Mock
private lateinit var disposable: Disposable
#Mock
lateinit var repoTest: RepoTest
#Before
fun setup() {
RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { t -> Schedulers.trampoline() }
MockitoAnnotations.initMocks(this)
repoTest = Mockito.mock(RepoTest::class.java)
filmsView = Mockito.mock(GetFlimsContract.View ::class.java)
filmsPresenter = FlimsPresenter(filmsView,repoTest )
}
#Test
fun `should lod item into views`() {
Mockito.`when`(repoTest.filmm()).thenReturn(Observable.just(Response("","",7, listOf(ResultsItem()))))
filmsPresenter.fetchFilms()
Mockito.verify(filmsView).showLoader(true)
Mockito.verify(filmsView).setAdapter(anyList())
Mockito.verify(filmsView).showLoader(false)
}
#Test
fun `should show error if network call fails`(){
Mockito.`when`(repoTest.filmm()).thenReturn(Observable.error(Exception("t")))
filmsPresenter.fetchFilms()
Mockito.verify(filmsView).showLoader(true)
Mockito.verify(filmsView).showError("t")
Mockito.verify(filmsView).showLoader(false)
}
}
your actually creating two API here.
every time fetchFilms() on presenter gets called a new API is created. you can not mock the API in this way.
best guess to fix it is to pass API object as a method parameter to fetchFilms() in the presenter. this way you can decide to provide a real API or a mock one
I have been facing an issue with subjects and TestSchedulers. My tests pass if I use a Trampoline scheduler but for some reason they fail if I use the TestScheduler.
Here's my sample test and relevant classes.
#RunWith(MockitoJUnitRunner::class)
class DemoViewModelTest {
//Error Mocks
private val actionsStream: PublishSubject<DemoContract.ViewEvent> = PublishSubject.create()
private lateinit var viewModel: DemoViewModel
private val handler = mock(DemoContract.Handler::class.java)
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
}
#Test
fun testUpdateCounter() {
actionsStream.onNext(DemoContract.ViewEvent.UpdateClick)
testScheduler.triggerActions()
verify(handler).onUpdate()
}
protected var testScheduler = TestScheduler()
protected var schedulersProvider: SchedulersProvider = object : SchedulersProvider() {
override fun mainThread(): Scheduler {
return testScheduler
}
override fun io(): Scheduler {
return testScheduler
}
override fun computation(): Scheduler {
return testScheduler
}
override fun newThread(): Scheduler {
return testScheduler
}
override fun trampoline(): Scheduler {
return testScheduler
}
override fun single(): Scheduler {
return testScheduler
}
}
}
And my ViewModel class
class DemoViewModel (val schedulersProvider: SchedulersProvider, val handler:DemoContract.Handler) : DemoContract.ViewModel() {
var viewEventsStream: Observable<DemoContract.ViewEvent>? = null
set(value) {
field = value
subscribeToViewEvents()
}
private fun subscribeToViewEvents() {
viewEventsStream?.let {
it.subscribeOn(schedulersProvider.io())
.observeOn(schedulersProvider.mainThread())
.subscribe(object:Observer<DemoContract.ViewEvent>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: DemoContract.ViewEvent) {
onViewEvent(t)
}
override fun onError(e: Throwable) {
}
})
}
}
fun onViewEvent(event: DemoContract.ViewEvent) {
when (event) {
is DemoContract.ViewEvent.UpdateClick -> {
handler.onUpdate()
}
}
}
}
and My Contract class is
interface DemoContract {
abstract class ViewModel
sealed class ViewEvent {
object UpdateClick : ViewEvent()
}
interface Handler{
fun onUpdate()
}
}
A few things about this, If I replace
viewModel.viewEventsStream = actionsStream
with
viewModel.viewEventsStream = Observable.just(DemoContract.ViewEvent.Update)
this test passes.
Can someone throw some light on this, thanks in advance. Here's the minimum runnable project
Thanks a lot #akarnokd for pointing me in the right direction. Turns out this is a race condition between the subscriber for the PublishSubject being added(subscribeActual call) and the onNext Call that I had trigger in my test. onNext call returns before the former happens.
The solution is to explicitly invoke triggerActions twice once immediately after subscription and once after emission.
Change this
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
}
To
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
testScheduler.triggerActions()
}