Open Claire1817 opened 5 years ago
Hi @ClaireGizard ,
I see a few problems there:
toothPickRule.inject(this)
, but I don't see any class being injected (the class under Test).WeatherApplicationModule
being installed inside the tests. Still, if you wanna use mocks for all your dependencies, you shouldn't need to install it.ToothPickRule
only grabs mocks from the annotations with name @Mock
, supporting Mockito and EasyMock. Do you want to create an issue so we add support to MockK
?Anyway, to provide you a working code, I'd need to see the code that you are testing: GetAddressUseCase
.
Hi @dlemures ! Sorry for my late response. I tried to find a solution by creating another simple project. I solved the first and the third problem but i didn't find a solution for the second. In my second project i have an error on the test and i didn't find a solution and i hope you can help me. Here there is my code:
package com.example.viewmodeltests
import android.app.Application
import android.location.Address
import android.location.Geocoder
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Provider
class GetAddressUseCase @Inject constructor() {
@Inject
lateinit var geocoder: Geocoder
private val latitude = 48.117266
private val longitude = -1.6777926
fun execute(): Single<Address> {
return Single.just(geocoder.getFromLocation(latitude, longitude, 1))
.filter { it.isNotEmpty() }
.map { it.first() }
.toSingle()
}
}
class GeocoderProvider(var application: Application) : Provider<Geocoder> {
///////////////////////////////////////////////////////////////////////////
// SPECIALISATION
///////////////////////////////////////////////////////////////////////////
override fun get(): Geocoder {
return Geocoder(application)
}
}
package com.example.viewmodeltests
import io.reactivex.Single
import javax.inject.Inject
class GetTextUseCase @Inject constructor() {
fun execute(): Single<String> {
return Single.just("Plaf")
}
}
package com.example.viewmodeltests;
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import kotlinx.android.synthetic.main.activity_main.*
import toothpick.Toothpick
import toothpick.config.Module
import javax.inject.Inject
class MainActivity : AppCompatActivity() {
@Inject
lateinit var viewModel: MainViewModelInterface
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
Toothpick.openScopes(application, this).also {
it.installModules(ActivityModule(this))
Toothpick.inject(this, it)
}
viewModel.observeText()
.observe(this, Observer {
it?.apply {
textView.text = it
}
})
viewModel.observeAddress()
.observe(this, Observer {
it?.apply {
address.text = it
}
})
button.setOnClickListener {
viewModel.updateText()
}
}
}
class ActivityModule(mainActivity: MainActivity) : Module() {
init {
bind(MainViewModelInterface::class.java).toInstance(ViewModelProviders.of(mainActivity).get(MainViewModel::class.java))
}
}
package com.example.viewmodeltests
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import toothpick.Toothpick
import javax.inject.Inject
class MainViewModel @Inject constructor(application: Application) : MainViewModelInterface, AndroidViewModel(application) {
@Inject
lateinit var getTextUseCase: GetTextUseCase
@Inject
lateinit var getAddressUseCase: GetAddressUseCase
private val text: MutableLiveData<String> = MutableLiveData()
private val address: MutableLiveData<String> = MutableLiveData()
private val disposableSubscriptions: CompositeDisposable = CompositeDisposable()
init {
Toothpick.openScopes(application, this).also {
Toothpick.inject(this, it)
}
text.postValue("Plouf")
address.postValue("Paris")
}
override fun observeText(): LiveData<String> {
return text
}
override fun observeAddress(): LiveData<String> {
return address
}
override fun onDestroy() {
disposableSubscriptions.dispose()
Toothpick.closeScope(this)
super.onCleared()
}
override fun updateText() {
getTextUseCase.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess {
text.postValue(it)
}
.subscribeWith(RxLogSingleSubscriber("getTextUseCase"))
.also {
disposableSubscriptions.add(it)
}
getAddressUseCase.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess {
address.postValue(it.locality)
}
.subscribeWith(RxLogSingleSubscriber("getAddressUseCase"))
.also {
disposableSubscriptions.add(it)
}
}
}
package com.example.viewmodeltests
import android.app.Application
import android.location.Geocoder
import toothpick.Toothpick
import toothpick.config.Module
import toothpick.configuration.Configuration
import toothpick.smoothie.module.SmoothieApplicationModule
class ViewModelTestsApplication : Application() {
override fun onCreate() {
super.onCreate()
initToothpick()
}
///////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////////////////
private fun initToothpick() {
val configuration = if (BuildConfig.DEBUG) Configuration.forDevelopment() else Configuration.forProduction()
Toothpick.setConfiguration(configuration)
Toothpick.openScope(this).let {
it.installModules(
ApplicationModule(this),
SmoothieApplicationModule(this)
)
}
}
class ApplicationModule(application: Application) : Module() {
init {
bind(Geocoder::class.java).toProviderInstance(GeocoderProvider(application))
}
}
}
And my test file:
package com.example.viewmodeltests
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.mockk
import io.mockk.verify
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.schedulers.Schedulers
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import toothpick.testing.ToothPickRule
class MainViewModelTests {
@get:Rule
var toothPickRule = ToothPickRule(this, "test scope")
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@InjectMockKs
lateinit var mainViewModel: MainViewModel
@Before
fun before() {
MockKAnnotations.init(this)
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
toothPickRule.inject(this)
}
@Test
fun checkTextAfterInitialization() {
// Given
val observer = mockk<Observer<String>>()
every { observer.onChanged(any()) } returns Unit
// When
mainViewModel.observeText().observeForever(observer)
// Then
verify {
observer.onChanged("Plouf")
}
}
// Test to check the state after a click
@Test
fun checkTextAfterClick() {
// Given
val observer = mockk<Observer<String>>()
every { observer.onChanged(any()) } returns Unit
// When
mainViewModel.observeText().observeForever(observer)
mainViewModel.updateText()
// Then
verify {
observer.onChanged("Plaf")
}
}
}
And there is my stacktrace:
io.mockk.MockKException: No matching constructors found:
constructor(application : android.app.Application = <not able to lookup>)
at io.mockk.impl.annotations.MockInjector.constructorInjection(MockInjector.kt:20)
at io.mockk.impl.annotations.JvmMockInitializer.doInjection(JvmMockInitializer.kt:86)
at io.mockk.impl.annotations.JvmMockInitializer.initMock(JvmMockInitializer.kt:64)
at io.mockk.impl.annotations.JvmMockInitializer.initAnnotatedMocks(JvmMockInitializer.kt:22)
at com.example.viewmodeltests.MainViewModelTests.before(MainViewModelTests.kt:88)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.mockito.internal.junit.JUnitRule$1.evaluateSafely(JUnitRule.java:52)
at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:43)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at toothpick.testing.ToothPickStatement.evaluate(ToothPickStatement.java:17)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
If succeded to do some tests on my use case but i failed on the viewModel. I don't understand why. Do you have an idea ?
Thank's
Claire
@Claire, I think your mockito rule is useless ;), even if it doesn't change the behavior/issue.
Le jeu. 1 août 2019 à 14:24, ClaireGizard notifications@github.com a écrit :
Hi @dlemures https://github.com/dlemures ! Sorry for my late response. I tried to find a solution by creating another simple project. I solved the first and the third problem but i didn't find a solution for the second. In my second project i have an error on the test and i didn't find a solution and i hope you can help me. Here there is my code:
package com.example.viewmodeltests
import android.app.Application import android.location.Address import android.location.Geocoder import io.reactivex.Single import javax.inject.Inject import javax.inject.Provider
class GetAddressUseCase @Inject constructor() {
@Inject lateinit var geocoder: Geocoder private val latitude = 48.117266 private val longitude = -1.6777926 fun execute(): Single<Address> { return Single.just(geocoder.getFromLocation(latitude, longitude, 1)) .filter { it.isNotEmpty() } .map { it.first() } .toSingle() }
}
class GeocoderProvider(var application: Application) : Provider
{ /////////////////////////////////////////////////////////////////////////// // SPECIALISATION /////////////////////////////////////////////////////////////////////////// override fun get(): Geocoder { return Geocoder(application) }
}
package com.example.viewmodeltests
import io.reactivex.Single import javax.inject.Inject
class GetTextUseCase @Inject constructor() {
fun execute(): Single<String> { return Single.just("Plaf") }
}
package com.example.viewmodeltests;
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import kotlinx.android.synthetic.main.activity_main.* import toothpick.Toothpick import toothpick.config.Module import javax.inject.Inject
class MainActivity : AppCompatActivity() {
@Inject lateinit var viewModel: MainViewModelInterface override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) Toothpick.openScopes(application, this).also { it.installModules(ActivityModule(this)) Toothpick.inject(this, it) } viewModel.observeText() .observe(this, Observer { it?.apply { textView.text = it } }) viewModel.observeAddress() .observe(this, Observer { it?.apply { address.text = it } }) button.setOnClickListener { viewModel.updateText() } }
}
class ActivityModule(mainActivity: MainActivity) : Module() { init { bind(MainViewModelInterface::class.java).toInstance(ViewModelProviders.of(mainActivity).get(MainViewModel::class.java)) } }
package com.example.viewmodeltests
import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers import toothpick.Toothpick import javax.inject.Inject
class MainViewModel @Inject constructor(application: Application) : MainViewModelInterface, AndroidViewModel(application) {
@Inject lateinit var getTextUseCase: GetTextUseCase @Inject lateinit var getAddressUseCase: GetAddressUseCase private val text: MutableLiveData<String> = MutableLiveData() private val address: MutableLiveData<String> = MutableLiveData() private val disposableSubscriptions: CompositeDisposable = CompositeDisposable() init { Toothpick.openScopes(application, this).also { Toothpick.inject(this, it) } text.postValue("Plouf") address.postValue("Paris") } override fun observeText(): LiveData<String> { return text } override fun observeAddress(): LiveData<String> { return address } override fun onDestroy() { disposableSubscriptions.dispose() Toothpick.closeScope(this) super.onCleared() } override fun updateText() { getTextUseCase.execute() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { text.postValue(it) } .subscribeWith(RxLogSingleSubscriber("getTextUseCase")) .also { disposableSubscriptions.add(it) } getAddressUseCase.execute() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { address.postValue(it.locality) } .subscribeWith(RxLogSingleSubscriber("getAddressUseCase")) .also { disposableSubscriptions.add(it) } }
}
package com.example.viewmodeltests
import android.app.Application import android.location.Geocoder import toothpick.Toothpick import toothpick.config.Module import toothpick.configuration.Configuration import toothpick.smoothie.module.SmoothieApplicationModule
class ViewModelTestsApplication : Application() {
override fun onCreate() { super.onCreate() initToothpick() } /////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS /////////////////////////////////////////////////////////////////////////// private fun initToothpick() { val configuration = if (BuildConfig.DEBUG) Configuration.forDevelopment() else Configuration.forProduction() Toothpick.setConfiguration(configuration) Toothpick.openScope(this).let { it.installModules( ApplicationModule(this), SmoothieApplicationModule(this) ) } } class ApplicationModule(application: Application) : Module() { init { bind(Geocoder::class.java).toProviderInstance(GeocoderProvider(application)) } }
}
And my test file:
package com.example.viewmodeltests
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.InjectMockKs import io.mockk.mockk import io.mockk.verify import io.reactivex.android.plugins.RxAndroidPlugins import io.reactivex.schedulers.Schedulers import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.junit.MockitoJUnit import toothpick.testing.ToothPickRule
class MainViewModelTests {
@get:Rule var mockitoRule = MockitoJUnit.rule() @get:Rule var toothPickRule = ToothPickRule(this, "test scope") @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() @InjectMockKs lateinit var mainViewModel: MainViewModel @Before fun before() { MockKAnnotations.init(this) RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } toothPickRule.inject(this) } @Test fun checkTextAfterInitialization() { // Given val observer = mockk<Observer<String>>() every { observer.onChanged(any()) } returns Unit // When mainViewModel.observeText().observeForever(observer) // Then verify { observer.onChanged("Plouf") } } // Test to check the state after a click @Test fun checkTextAfterClick() { // Given val observer = mockk<Observer<String>>() every { observer.onChanged(any()) } returns Unit // When mainViewModel.observeText().observeForever(observer) mainViewModel.updateText() // Then verify { observer.onChanged("Plaf") } }
}
And there is my stacktrace:
io.mockk.MockKException: No matching constructors found: constructor(application : android.app.Application =
) at io.mockk.impl.annotations.MockInjector.constructorInjection(MockInjector.kt:20) at io.mockk.impl.annotations.JvmMockInitializer.doInjection(JvmMockInitializer.kt:86) at io.mockk.impl.annotations.JvmMockInitializer.initMock(JvmMockInitializer.kt:64) at io.mockk.impl.annotations.JvmMockInitializer.initAnnotatedMocks(JvmMockInitializer.kt:22) at com.example.viewmodeltests.MainViewModelTests.before(MainViewModelTests.kt:88) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24) at org.mockito.internal.junit.JUnitRule$1.evaluateSafely(JUnitRule.java:52) at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:43) at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55) at toothpick.testing.ToothPickStatement.evaluate(ToothPickStatement.java:17) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
If succeded to do some tests on my use case but i failed on the viewModel. I don't understand why. Do you have an idea ?
Thank's
Claire
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/stephanenicolas/toothpick/issues/333?email_source=notifications&email_token=ABSAEWIVQNPXDC7U5DHSJ3TQCLIV3A5CNFSM4HSIUAYKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3KNBJY#issuecomment-517263527, or mute the thread https://github.com/notifications/unsubscribe-auth/ABSAEWKFKCES47QJY2MSNDLQCLIV3ANCNFSM4HSIUAYA .
effectively, i deleted it !
Hi @ClaireGizard ,
Sorry for the late response, we have been quite busy releasing the 3rd version.
The error you are having is with mockK
. It is trying to find a mock of Application
to inject in the MainViewModel
constructor, but you haven't defined any:
class MainViewModelTests {
@get:Rule
var toothPickRule = ToothPickRule(this, "test scope")
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@InjectMockKs // <- USING MOCKK TO INJECT THE DEPENDENCIES
lateinit var mainViewModel: MainViewModel
@Before
fun before() {
MockKAnnotations.init(this) // <- YOU DONT HAVE ANY MOCK ANNOTATION
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
toothPickRule.inject(this) // <- DOING NOTHING
}
}
Even though you are using toothPickRule.inject(this)
, you are not using TP to inject MainViewModel
. You are using mockK instead. You need to use one or the other.
I've not used mockK before, but if you decide to with that option, I think that something like this should work:
class MainViewModelTests {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@MockK
lateinit var application: Application
@InjectMockKs
lateinit var mainViewModel: MainViewModel
@Before
fun before() {
MockKAnnotations.init(this)
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
}
}
Hello !
I didn't find a solution in the others issues or in the samples.
I think this case can help the others developpers. I'm trying to do some simple tests on my useCase and i use Mockk and jUnit.
I have an error in the injection of my object geocoder, this is the stacktrace of my error:
My useCase:
and my test file (my test does nothing actually, i will improve my test when my problem will be solve).
I also have a binding file which looks like that:
I install my modules in the application. My useCase is call in a viewModel which doesn't contain TP.
I also try to Inject my useCase directly in my test class but it's not working.
Does someone have an idea ?
Thank's !