I need to write the test for Fragment and I need it takes a data from fake repository. I created fake Fragment for test with val fragmentScenario = launchFragmentInContainer and initialized repository with a fake repository but if I just use real view model it is try to take a data from real repository. So I guess I have to use fake ViewModel to take a data from fake repository. But I didnt figured out how to fake it. How can I fake it?
#RunWith(AndroidJUnit4::class)
#ExperimentalCoroutinesApi
class WeatherForecastFragmentTest {
private lateinit var weatherRepository: WeatherRepository
private lateinit var locationRepository: LocationRepository
private lateinit var weatherViewModel: WeatherForecastViewModel
#Before
fun initRepository() = runTest {
locationRepository = FakeAndroidLocationRepository()
weatherRepository = FakeAndroidWeatherRepository()
weatherViewModel = WeatherForecastViewModel(weatherRepository, locationRepository)
cleanDb()
}
#Test
fun initViewModel_loadListWeather_DisplayedInUi() = runBlockingTest {
val location = Location(1, "Moscow")
val bundle = WeatherForecastFragmentArgs(location.id, location.name).toBundle()
val fragmentScenario = launchFragmentInContainer<WeatherForecastFragment>(
bundle, R.style.ThemeOverlay_AppCompat_Light
)
Espresso.onView(ViewMatchers.withId(R.id.weatherForecastRecyclerView))
.check(matches(isDisplayed()))```
Related
I have view model which can be provided via such code:
val retrofitService = RetrofitService.getInstance(requireContext())
val mainRepository = MainRepository(retrofitService)
val viewVM = ViewModelProvider(this, AppVMFactory(mainRepository)).get(AppViewModel::class.java)
and I would like to test my viewmodel requests and so on. Due to the fact that I will need context for my test I decided to use instrumented test where I can get the context via such line:
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
the problem that I have is connected with getting lifecycle owner inside the test. The ViewModelProvider has such constructors:
constructor(
private val store: ViewModelStore,
private val factory: Factory,
private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)
and:
constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
owner.viewModelStore,
factory,
defaultCreationExtras(owner)
)
they are very similar. But how I can create viewmodel inside my test? Is it possible or it only can be done via totally different way?
This is my solution of this problem:
private val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
#Test
fun simpleLogIn() {
val scenario = launchActivity<HomeScreen>()
scenario.onActivity { activity ->
val retrofitService = RetrofitService.getInstance(appContext)
val mainRepository = MainRepository(retrofitService)
val viewVM =
ViewModelProvider(
activity,
AppVMFactory(mainRepository)
)[AppViewModel::class.java]
}
}
here as you can see I use launchActivity scenario. To enable it you should use in the build.gradle such dependency:
androidTestImplementation 'androidx.test:core-ktx:X.X.X'
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() != "")
}
}
Need some help with testing my app.
I want to verify viewmodel fun writeNewTask(). It has to add Task object into database under the parents called tasks and personal id of every Task.
fun writeNewTask(name: String?, orphanage: String?) {
val id = database.push().key!!
val task = Task(id, name, orphanage)
database.child("tasks").child(id).setValue(task)
}
I'm using PowerMock. So, that's how my test looks like
#RunWith(PowerMockRunner::class)
#PowerMockRunnerDelegate(JUnit4::class)
#PrepareForTest(FirebaseDatabase::class, AddTaskViewModel::class, DatabaseReference::class)
#PowerMockIgnore("org.mockito.*", "org.robolectric.*", "android.*", "androidx.*")
class AddTaskViewModelTest {
#Mock
lateinit var mockedDatabase: FirebaseDatabase
#Mock
lateinit var databaseReference: DatabaseReference
#Mock
lateinit var viewModel: AddTaskViewModel
#Before
fun before() {
viewModel = PowerMockito.mock(AddTaskViewModel::class.java)
databaseReference = PowerMockito.mock(DatabaseReference::class.java)
PowerMockito.mockStatic(AddTaskViewModel::class.java)
PowerMockito.mockStatic(DatabaseReference::class.java)
PowerMockito.mockStatic(FirebaseDatabase::class.java)
`when`(FirebaseDatabase.getInstance()).thenReturn(mockedDatabase)
mockedDatabase = PowerMockito.mock(FirebaseDatabase::class.java)
`when`(mockedDatabase.reference).thenReturn(databaseReference)
}
#Test
fun writeNewTaskTest() {
val id = "1"
`when`(databaseReference.key).thenReturn(id)
val name = "name"
val orphanage = "orphanage"
val task = Task(id, name, orphanage)
viewModel.writeNewTask(name, orphanage)
verify(databaseReference)
.child("tasks")
.child(id)
.setValue(task)
}
}
And I catch this error. After debugging I figured out that child("tasks") is null
databaseReference.child("tasks");
-> at com.example.application.viewModel.AddTaskViewModelTest.writeNewTaskTest(AddTaskViewModelTest.kt:59)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
databaseReference.child("tasks");
-> at com.example.application.viewModel.AddTaskViewModelTest.writeNewTaskTest(AddTaskViewModelTest.kt:59)
Actually, there were zero interactions with this mock.
This happens because the reference you have to your database is not the same as you have in the ViewModel. So let's pretend you have a ViewModel that gets a database in it's contructor as such:
class AddTaskViewModel(private val database: FirebaseDatabase): ViewModel()
So, to test your ViewModel you need to create its instance, not mock it. Which in your code should look like:
#Before
fun before() {
databaseReference = mock(DatabaseReference::class.java)
mockedDatabase = mock(FirebaseDatabase::class.java)
`when`(mockedDatabase.reference).thenReturn(databaseReference)
viewModel = AddTaskViewModel(mockedDatabase)
}
Also you don't need PowerMockito to test this, Mockito itself is enough
I am new to JUnit test cases in Android. I heard that by using Mockito we can achieve easily.
My Android Class makes an external call to a REST API Service (Retrofit) that returns a JSON response. I have to mock that response (hardcoded JSON) and write test cases.
Please share your idea how to achieve this.
i am able to achieve.
updating with a piece of code.
class GenerateTripViewModelTest {
#Mock
private lateinit var mockRepository: GenerateTripRepository
private val schedulerProvider = SchedulerProvider(Schedulers.trampoline(), Schedulers.trampoline())
private lateinit var generateTripViewModel: GenerateTripViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
generateTripViewModel = GenerateTripViewModel(mockRepository, schedulerProvider)
}
#Test
fun SearchSuccessCase() {
val tripReq = GenerateTripReqModel(ArrayList<String>(),"123","xxxx","xxxx")
Mockito.`when`(mockRepository.generateTrip(tripReq)).thenReturn(Observable.just(GenerateTripResModel("")))
val testObserver = TestObserver<GenerateTripResModel>()
generateTripViewModel.generateTrip(tripReq).subscribe(testObserver)
testObserver.assertNoErrors()
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
class GenerateTripViewModel #Inject constructor(private val generateTripRepository: GenerateTripRepository,private val schedulerProvider: SchedulerProvider) : ViewModel() {
fun generateTrip(reqModel: GenerateTripReqModel) = generateTripRepository.generateTrip(reqModel)
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#Singleton
class GenerateTripRepository #Inject constructor(
private val apiServices: ApiServices, private val context: Context,
private val appExecutors: AppExecutors = AppExecutors()) {
/**
* Generate Trip
*/
fun generateTrip(reqModel: GenerateTripReqModel): Observable<GenerateTripResModel> = apiServices.generateTrip(reqModel)
}
class Test{
#Mock
lateinit var redditApiService: RedditApiService
lateinit var postSettingsViewModel: PostSettingsViewModel
#Before
fun setUp() {
initMocks(this)
postSettingsViewModel = PostSettingsViewModel(redditApiService, userRepo)
}
#Test
fun testApi(){
Mockito.`when`(redditApiService.getSubreddits("asd")).thenReturn(Single
.just<SubredditResponse>(SubredditResponse(listOf(Subreddit("first")))))
//make your tests
}
}
You can use viewmodel or presenter and pass your api service there. With mockito you can specify function call and its return value. Mockito.when(api.get()).thenReturn(new Result()).
You can check google samples: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/test/java/com/android/example/github/api/GithubServiceTest.kt
You can use product flavors for mock your response or user MockWebServer library or replace your retrofit interface with you implementation what return json from assets
upd: I misunderstood the question. I agree with Phowner Biring
I'm currently writing some UI unit tests for a fragment, and one of these #Test is to see if a list of objects is correctly displayed, this is not an integration test, therefore I wish to mock the ViewModel.
The fragment's vars:
class FavoritesFragment : Fragment() {
private lateinit var adapter: FavoritesAdapter
private lateinit var viewModel: FavoritesViewModel
#Inject lateinit var viewModelFactory: FavoritesViewModelFactory
(...)
Here's the code:
#MediumTest
#RunWith(AndroidJUnit4::class)
class FavoritesFragmentTest {
#Rule #JvmField val activityRule = ActivityTestRule(TestFragmentActivity::class.java, true, true)
#Rule #JvmField val instantTaskExecutorRule = InstantTaskExecutorRule()
private val results = MutableLiveData<Resource<List<FavoriteView>>>()
private val viewModel = mock(FavoritesViewModel::class.java)
private lateinit var favoritesFragment: FavoritesFragment
#Before
fun setup() {
favoritesFragment = FavoritesFragment.newInstance()
activityRule.activity.addFragment(favoritesFragment)
`when`(viewModel.getFavourites()).thenReturn(results)
}
(...)
// This is the initial part of the test where I intend to push to the view
#Test
fun whenDataComesInItIsCorrectlyDisplayedOnTheList() {
val resultsList = TestFactoryFavoriteView.generateFavoriteViewList()
results.postValue(Resource.success(resultsList))
(...)
}
I was able to mock the ViewModel but of course, that's not the same ViewModel created inside the Fragment.
So my question really, has someone done this successfully or has some pointers/references that might help me out?
Also, I've tried looking into the google-samples but with no luck.
For reference, the project can be found here: https://github.com/JoaquimLey/transport-eta/
Within your test setup you'll need to provide a test version of the FavoritesViewModelFactory which is being injected in the Fragment.
You could do something like the following, where the Module will need to be added to your TestAppComponent:
#Module
object TestFavoritesViewModelModule {
val viewModelFactory: FavoritesViewModelFactory = mock()
#JvmStatic
#Provides
fun provideFavoritesViewModelFactory(): FavoritesViewModelFactory {
return viewModelFactory
}
}
You'd then be able to provide your Mock viewModel in the test.
fun setupViewModelFactory() {
whenever(TestFavoritesViewModelModule.viewModelFactory.create(FavoritesViewModel::class.java)).thenReturn(viewModel)
}
I have solved this problem using an extra object injected by Dagger, you can find the full example here: https://github.com/fabioCollini/ArchitectureComponentsDemo
In the fragment I am not using directly the ViewModelFactory, I have defined a custom factory defined as a Dagger singleton:
https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearch/src/main/java/it/codingjam/github/ui/search/SearchFragment.kt
Then in the test I replace using DaggerMock this custom factory using a factory that always returns a mock instead of the real viewModel:
https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearchTest/src/androidTest/java/it/codingjam/github/ui/repo/SearchFragmentTest.kt
Look like, you use kotlin and koin(1.0-beta).
It is my decision for mocking
#RunWith(AndroidJUnit4::class)
class DashboardFragmentTest : KoinTest {
#Rule
#JvmField
val activityRule = ActivityTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
#Rule
#JvmField
val countingAppExecutors = CountingAppExecutorsRule()
private val testFragment = DashboardFragment()
private lateinit var dashboardViewModel: DashboardViewModel
private lateinit var router: Router
private val devicesSuccess = MutableLiveData<List<Device>>()
private val devicesFailure = MutableLiveData<String>()
#Before
fun setUp() {
dashboardViewModel = Mockito.mock(DashboardViewModel::class.java)
Mockito.`when`(dashboardViewModel.devicesSuccess).thenReturn(devicesSuccess)
Mockito.`when`(dashboardViewModel.devicesFailure).thenReturn(devicesFailure)
Mockito.`when`(dashboardViewModel.getDevices()).thenAnswer { _ -> Any() }
router = Mockito.mock(Router::class.java)
Mockito.`when`(router.loginActivity(activityRule.activity)).thenAnswer { _ -> Any() }
StandAloneContext.loadKoinModules(hsApp + hsViewModel + api + listOf(module {
single(override = true) { router }
factory(override = true) { dashboardViewModel } bind ViewModel::class
}))
activityRule.activity.setFragment(testFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#After
fun tearDown() {
activityRule.finishActivity()
StandAloneContext.closeKoin()
}
#Test
fun devicesSuccess(){
val list = listOf(Device(deviceName = "name1Item"), Device(deviceName = "name2"), Device(deviceName = "name3"))
devicesSuccess.postValue(list)
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name1Item"))))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name2"))))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name3"))))
}
#Test
fun devicesFailure(){
devicesFailure.postValue("error")
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
Mockito.verify(router, times(1)).loginActivity(testFragment.activity!!)
}
#Test
fun devicesCall() {
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
Mockito.verify(dashboardViewModel, Mockito.times(1)).getDevices()
}
}
In the example you provided, you are using mockito to return a mock for a specific instance of your view model, and not for every instance.
In order to make this work, you will have to have your fragment use the exact view model mock that you have created.
Most likely this would come from a store or a repository, so you could put your mock there? It really depends on how you setup the acquisition of the view model in your Fragments logic.
Recommendations:
1) Mock the data sources the view model is constructed from or
2) add a fragment.setViewModel() and Mark it as only for use in tests. This is a little ugly, but if you don't want to mock data sources, it is pretty easy this way.
One could easily mock a ViewModel and other objects without Dagger simply by:
Create a wrapper class that can re-route calls to the ViewModelProvider. Below is the production version of the wrapper class that simply passes the calls to the real ViewModelProvider which is passed in as a parameter.
class VMProviderInterceptorImpl : VMProviderInterceptor { override fun get(viewModelProvider: ViewModelProvider, x: Class<out ViewModel>): ViewModel {
return viewModelProvider.get(x)
}
}
Adding getters and setters for this wrapper object to the Application class.
In the Activity rule, before an activity is launched, swap out the real wrapper with a mocked wrapper that does not route the get ViewModel call to the real viewModelProvider and instead provides a mocked object.
I realize this is not as powerful as dagger but the simplicity is attractive.