I have problem while trying to use Robolectric to test activity which is using EasyMvp.
All of the classes are written in Kotlin.
This is beginning of an activity:
#ActivityView(layout = R.layout.activity_access, presenter = AccessPresenterImpl::class)
class AccessActivity : BaseActivity(), AccessView {
#Presenter
lateinit var presenter: AccessPresenter
override fun providePresenter(): BasePresenter? {
return presenter
}
And at the onStart, every activity is initializing extras in presenter.
I was trying to introduce Robolectric tests in my app.
var activity: AccessActivity? = null
var loginEditText: EditText? = null
var passwordEditText: EditText? = null
#Before
fun initData() {
activity = Robolectric.setupActivity(AccessActivity::class.java)
loginEditText = activity?.findViewById(R.id.loginEditText)
passwordEditText = activity?.findViewById(R.id.passwordEditText)
}
But while Running the test, I am always getting error:
kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
I assume that this is problem with Robolectric annotation proccessing. Is there any solution for that?
Related
I want to create a mock Object of an Activity with annotation #AndroidEntryPoint. After that mock it's methods like
whenever(activity.getAnalytics()).thenReturn(mockOfAnalytics)
but it doesn't work for activity annotated by #AndroidEntryPoint, when I remove this annotation it works - build.tool 4.2.2
#RunWith(PowerMockRunner::class)
#PrepareForTest(Html::class)
class CallShopViewModelTest {
...
#Mock
private lateinit var storefrontAnalytics: StorefrontAnalytics
#Mock
private lateinit var storefrontDelegate: StorefrontActivity.StorefrontDelegate
...
#Test
fun setShopObject() {
val mock = Mockito.mock(ShopMenuActivity::class.java)
whenever(mock.storefrontAnalytics).thenReturn(storefrontAnalytics)
whenever(mock.storefrontDelegate).thenReturn(storefrontDelegate)
whenever(mock.getString(ArgumentMatchers.anyInt(),
ArgumentMatchers.anyString())).thenReturn("test")
objectUnderTest = CallShopViewModel(mock)
objectUnderTest.setShop(Shop().apply {
isAcquired = false
shopId = 100
twilioPhone = "123"
})
Assert.assertFalse(objectUnderTest.mAcquired.get())
Assert.assertTrue(objectUnderTest.twilioFormattedText.get() != "")
}
}
Activity
#AndroidEntryPoint
class ShopMenuActivity : StorefrontActivity {
....
val storefrontAnalytics: StorefrontAnalytics
get() = app.storefrontAnalytics
val storefrontDelegates: StorefrontDelegates
get() = app.storefrontDelegates
}
So How can I mock this activity and use it's methods?
Thanks!
You better inject storefrontAnalytics: StorefrontAnalytics and val storefrontDelegates: StorefrontDelegates. Hilt plugin is rewriting your #AndroidEntryPoint annotated to a different decorator, this is probably the reason you are getting different results.
If you want to unit test your view model, you should not bring in the activity that hosts it. Provide mocked dependencies directly to the view model. Also why you are providing the activity to the view model constructor? This makes your unit testing excruciatingly painful.
Something like this:
#RunWith(PowerMockRunner::class)
#PrepareForTest(Html::class)
class CallShopViewModelTest {
...
#Mock
private lateinit var storefrontAnalytics: StorefrontAnalytics
#Mock
private lateinit var storefrontDelegate: StorefrontActivity.StorefrontDelegate
...
#Test
fun setShopObject() {
val mockStorefrontAnalytics : ... = mock()
val mockStorefrontDelegate : ... = mock()
whenever(mockStorefrontAnalytics).thenReturn(storefrontAnalytics)
whenever(mockStorefrontDelegate).thenReturn(storefrontDelegate)
objectUnderTest = CallShopViewModel(mockmockStorefrontAnalytics, mockStorefrontDelegate)
objectUnderTest.setShop(Shop().apply {
isAcquired = false
shopId = 100
twilioPhone = "123"
})
Assert.assertFalse(objectUnderTest.mAcquired.get())
Assert.assertTrue(objectUnderTest.twilioFormattedText.get() != "")
}
}
I have a class with multiple variables inside.
SomeViewModel has a boolean variable defaulted to false,
var booleanVariable = false
SomeViewModel depends on SomeDataModel,
var dataModel: SomeDataModel? = null
Test class
#RunWith(MockitoJUnitRunner::class)
class TestClass {
#Mock lateinit var someViewModel: SomeViewModel
#Mock lateinit var someDataModel: SomeDataModel
#Before
fun setup() {
when(someViewModel.booleanVariable).thenReturn(true)
when(someViewModel.dataModel).thenReturn(someDataModel)
}
#Test
fun shouldShowImportPolicyTest() {
someViewModel.booleanVariable // return FALSE, not true as stubbed
someViewModel.dataModel // always returns NULL
}
}
if I stub a method it works fine. what am I doing wrong here?
You can mock member variables if they are not final with Mockito.
You have 3 choices here:
make them open
use the dependency mockito-inline to allow the mocking on final classes/fields
use the Kotlin all-open compiler plugin to make the class and its fields open in tests (https://kotlinlang.org/docs/reference/compiler-plugins.html)
I have simple application based on mvp. Write test for presenter. Used Mockito for mock data. I catch view callback data (ArrayList) with ArgumentCaptor. My Test class
#RunWith(MockitoJUnitRunner::class)
class MainPresenterTest{
#Mock
lateinit var view:MainView
#Mock
lateinit var context:Context
#InjectMocks
lateinit var presenter: MainPresenter
#Captor
lateinit var captor: ArgumentCaptor<ArrayList<News>>
#Before
fun init(){
MockitoAnnotations.initMocks(this)
}
#Test
fun success(){
presenter.loadNews()
Mockito.verify<MainView>(view).onSuccess(captor.capture())
var data = captor.value
Mockito.verify(view).onSuccess(data)
Mockito.verify(view,never()).onError("")
}
}
Main View
interface MainView{
fun onSuccess(n:ArrayList<News>)
fun onError(e:String)
}
But throw
java.lang.IllegalStateException: captor.capture() must not be null
Example of correct verification:
verify(mock).doSomething()
Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
You actually don't need to define it as a class lateinit variable. In your test method, define a method variable like this
#Test
fun success(){
presenter.loadNews()
val captor: ArgumentCaptor<ArrayList<*>> = ArgumentCaptor.forClass(ArrayList::class.java)
Mockito.verify<MainView>(view).onSuccess(captor.capture())
var data = captor.value
Mockito.verify(view).onSuccess(data)
Mockito.verify(view,never()).onError("")
}
}
Also, you should assert the data from the captor. Instead of this
Mockito.verify(view).onSuccess(data)
do something like this
assertEquals("x", data.size())
I'm trying to write an espresso test in my application. In the application, I use dagger 2 and architecture components (LiveData, etc.). The test has a function to help me create fake injections for the tested activity. When I use it to mock the ManViewModel it works with no problem and I can run the test. But when I want to set a mock value for the ViewModelProvider.Factory the test throws an error in MainActivity: ViewModelProviders.of(th…iewModelImpl::class.java) must not be null
I have debugged the test and when I'm assigning the mock value it isn't null and in the main activity the values are not null but steel I get the error.
Some help would be appreciated.
The code for MainActivity:
class MainActivity : BaseActivity(), AnimateFactsImage {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
#Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mainViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
// Here is the error: ViewModelProviders.of(th…iewModelImpl::class.java) must not be null
}
}
The code for MainActiviyTest:
#RunWith(AndroidJUnit4::class)
class MainActivityView {
#get:Rule
val activityTestRule = object : ActivityTestRule<MainActivity>(MainActivity::class.java, true, false) {
override fun beforeActivityLaunched() {
super.beforeActivityLaunched()
val myApp = InstrumentationRegistry.getTargetContext().applicationContext as MyApplication
myApp.activityDispatchingAndroidInjector = createFakeActivityInjector<MainActivity> {
mainViewModel = mainView
viewModelFactory = mockMPF // TODO: fix problem, null pointer in activity
}
}
}
private val mockMPF = Mockito.mock(ViewModelProvider.Factory::class.java)
private var mainView = Mockito.mock(MainViewModel::class.java)
private val repoLiveData = MutableLiveData<ApiResponse<Result>>()
#Before
fun setup() {
Mockito.`when`(mainView.getFactsList()).thenReturn(repoLiveData)
}
#Test
fun isListDisplayed() {
repoLiveData.postValue(ApiResponse(Result("title", arrayListOf(Row("title", "des", "img"))), null))
activityTestRule.launchActivity(null)
onView(withId(R.id.recycler_adapter)).check(matches(isDisplayed()))
}
}
I am trying to implement the integration of a Dialogflow (previously api.ai) agent with my Android app, using Kotlin. I checked other Q&A about kotlin lateinit and the onCreate() lifecycle in Android is ideal for late-init implementations, to avoid writing dirty code with null objects and corresponding !! and ? accesses in Kotlin. But I am running into the error of 'Property getter or setter expected' when trying to lateinint instances of self-defined class. Here is the code:
class AIApplication : Application() {
private var activitiesCount: Int = 0
var lateinit settingsManager: SettingsManager
//private set
private val isInForeground: Boolean
get() = activitiesCount > 0
override fun onCreate() {
super.onCreate()
settingsManager = SettingsManager(this)
}
Which gives me the error of 'Property getter or setter expected' on the lateinit settingsManager line. Here is the SettingsManager definition:
class SettingsManager(private val context: Context) {
private val prefs: SharedPreferences
private var useBluetooth: Boolean = false
var isUseBluetooth: Boolean
get() = useBluetooth
set(useBluetooth) {
this.useBluetooth = useBluetooth
prefs.edit().putBoolean(PREF_USE_BLUETOOTH, useBluetooth).commit()
val controller = (context.applicationContext as AIApplication).getBluetoothController()
if (useBluetooth) {
controller!!.start()
} else {
controller!!.stop()
}
}
init {
prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
useBluetooth = prefs.getBoolean(PREF_USE_BLUETOOTH, true)
}
companion object {
private val SETTINGS_PREFS_NAME = "ai.api.APP_SETTINGS"
private val PREF_USE_BLUETOOTH = "USE_BLUETOOTH"
}
}
So what is the proper way to do this? Do I need to make some changes to the SettingsManager class? Please explain the whole concept clearly.
The lateinit declaration of the SettingsManager is wrong. Try this :
lateinit var settingsManager: SettingsManager
instead of
var lateinit settingsManager: SettingsManager
Hope this helps.