How do I use activityScenarioRule<Activity>? - android

I'm following the dev guide here:
https://developer.android.com/guide/components/activities/testing
and have a test class like:
#RunWith(AndroidJUnit4::class)
class MyTestSuite {
#get:Rule var activityScenarioRule = activityScenarioRule<MyActivity>()
#Test fun testEvent() {
val scenario = activityScenarioRule.scenario
}
}
the method activityScenarioRule<T>() is not defined. What dependency do I need? Also, what is the best way to determine which dependencies to add when reading these docs?

The activityScenarioRule<T>() method is part of the androidx.test.ext:junit-ktx:1.1.0 dependency.
Usually, this would be listed under the List of AndroidX Test dependencies, but it appears it isn't up to date with the junit-ktx or core-ktx modules as of yet, despite it being explicitly mentioned as part of the Version 1.1.0-beta01 release notes

If you are not using the ktx dependency, e.g. androidx.test.ext:junit:1.1.2 you can do it like this:
#get:Rule
var activityScenarioRule = ActivityScenarioRule(MyActivity::class.java)

I had a similar problem.
Changing dependency from testImplementation 'androidx.test.ext:junit:1.1.3'
to androidTestImplementation 'androidx.test.ext:junit:1.1.3' worked for me.

My example of usage ActivityScenarioRule<> for Java:
public class AdMobContainerImplTest {
private static final String TAG = AdMobContainerImplTest.class.getSimpleName();
#Rule
public ActivityScenarioRule<ENDetailsActivity> mActivityRule = new ActivityScenarioRule<>(
ENDetailsActivity.class);
#Test
public void testAdVisibility() {
mActivityRule.getScenario().onActivity(activity -> {
AdView ad = activity.findViewById(R.id.ad_banner);
ad.setAdListener(new AdListener() {
#Override
public void onAdLoaded() {
super.onAdLoaded();
Log.i(TAG, "ad loaded");
Assert.assertNotNull(ad);
onView(withId(R.id.ad_banner)).check(matches(isDisplayed()));
}
});
});
}
}

Related

Cannot resolve method 'plant(timber.log.Timber.DebugTree)'

What is wrong with my configuration or code ?
I have this error highlighted
Cannot resolve method 'plant(timber.log.Timber.DebugTree)'
for the code
import timber.log.Timber;
public class AppClass extends Application {
#Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); }
}
}
but it builds and it executes. Still I think it means something, no ?
Configuration infos:
Android Studio Bumblebee | 2021.1.1
classpath 'com.android.tools.build:gradle:7.1.0'
Gradle: com.jakewharton.timber:timber:5.0.1#aar
ext.kotlin_version = '1.6.10'
sourceCompatibility JavaVersion.VERSION_1_8
Until issue fixed (as #n8yn8 noted in question comment) I solved it with downgrade to version 4.7.1:
implementation 'com.jakewharton.timber:timber:4.7.1'
In app level build.gradle file, set the following jakewharton timber version:
implementation 'com.jakewharton.timber:timber:4.7.1'
Then in your application class onCreate() Method:
For Kotlin:
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
} else {
Timber.plant(ReleaseTree())
}
For Java:
if (BuildConfig.DEBUG) {
Timber.plant(new DebugTree());
} else {
Timber.plant(new ReleaseTree());
}
Inner ReleaseTree() class Kotlin:
inner class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return
}
// log your crash to your favourite
// Sending crash report to Firebase CrashAnalytics
// FirebaseCrash.report(message);
// FirebaseCrash.report(new Exception(message));
}
}
Inner ReleaseTree() class Java:
class ReleaseTree extends Timber.Tree {
#Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return;
}
// log your crash to your favourite
// Sending crash report to Firebase CrashAnalytics
// FirebaseCrash.report(message);
// FirebaseCrash.report(new Exception(message));
}
}
For the workaround solution without downgrade dependency version and also no need to apply with another dependency by keep applying the one from JakeWharton, we can try to config Timber in Kotlin instead of Java class since the warning message only appear on Java class.
By doing so, you can try two options bellow:
Convert your custom application class from Java to Kotlin
Create another class in Kotlin and create new method to config Timber with sample bellow:
TimberUtils.kt
import timber.log.Timber
object TimberUtils {
#JvmStatic
fun configTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
YourCustomJavaClass.java
#Override
public void onCreate() {
super.onCreate();
TimberUtils.configTimber();
}
Hope it can resolve your problem.
For those using sentry-timber
Just use
implementation "io.sentry:sentry-android:$sentry_version"
implementation "io.sentry:sentry-android-timber:$sentry_version"
Remove this dependency
implementation "com.jakewharton.timber:timber:$timber_version"
For me, this fix resolves the issue

Getting null pointer object on hilt inject object in activity when run test cases

Please check below my Module class in which I have defined my object which need to be inject using Hilt
NVModule.kt
#Module
#InstallIn(SingletonComponent::class)
class NVModule {
#Provides
#Named("ProfileHelper")
fun abprovideProfileHelper(): ProfileHelper {
return ProfileHelper(AppController.getInstance())
}
}
And now please check my Interface by which i have used the EntryPoint to access my dependency injection outside the Activity/Fragment like Helper class.
#EntryPoint
#InstallIn(SingletonComponent.class)
public interface CommonHiltInterface {
#Named("ProfileHelper")
public ProfileHelper provideProfileHelper();
}
}
Now please check the my Activity class on which i have used the dependency injection like below and it is working fine here. Means getting dependency injection properly
public class HomeActivity extends BaseActivity{
private ActivityHomescreenBinding
activityHomescreenBinding;
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
activityHomescreenBinding =
DataBindingUtil.inflate(getLayoutInflater(),
R.layout.activity_homescreen, null, false);
setContentView(activityHomescreenBinding.getRoot());
CommonHiltInterface commonHiltInterface = EntryPointAccessors.fromApplication(context, CommonHiltInterface.class);
commonHiltInterface.provideProfileHelper().setData();
}
}
But in case of the Test cases , dependency injection getting NullPointerException . I am using the Robolectric for the test cases. Please check my below lines of code for the RobolectricTest case.
#HiltAndroidTest
#RunWith(RobolectricTestRunner.class)
#Config(application = HiltTestApplication.class,
sdk = Build.VERSION_CODES.N, manifest = Config.NONE)
public class HomeActivityTest {
#Rule
public HiltAndroidRule hiltRule = new
HiltAndroidRule(this);
#Before
public void setUp() throws Exception {
shadowOf(Looper.getMainLooper()).idle();
hiltRule.inject();
activity =
Robolectric.buildActivity(HomeActivity.class).
create().resume().get();
}
}
Note :- 1). I have also use #HiltAndroidApp() for application class.and using 2.36 version for hilt dependency
2). My dependency injection working fine for the Java classes like Activity/Fagment and Helper classes , But not working in test cases.
Please check my dependency for Hilt are as follow
testImplementation 'com.google.dagger:hilt-android-testing:2.36'
kaptTest 'com.google.dagger:hilt-android-compiler:2.36'
testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.36'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.36'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.36'
Application runs successfully but in case of Test case I am getting the null pointer exception below lines of code in Activity (HomeActivity).
CommonHiltInterface commonHiltInterface = EntryPointAccessors.fromApplication(context, CommonHiltInterface.class);
commonHiltInterface.provideProfileHelper().setData();

Android instrumented test freezes when it tests a suspend function that uses RoomDatabase.withTransaction

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))
}
}

Unit test with LiveData Method getMainLooper in android.os.Looper not mocked

I cannot make liveData.postValue working in while trying to make unit test. I have been checking in google for a solution and this is the code I have now.
public class ProjectListViewModelTest {
GetProjectList getProjectList = Mockito.mock(GetProjectList.class);
ProjectModel.Project project = new ProjectModel.Project("testing",
"this is a test",
"https://logo.jpg",
new ProjectModel.Company("cat"),
"20150404",
"active");
List<ProjectModel.Project> projects = Arrays.asList(project);
ProjectModel.ProjectList projectsList = new ProjectModel.ProjectList(projects);
ProjectsListViewModel projectsListViewModel;
private PublishSubject<ProjectModel.ProjectList> projectsListPublishSubject = PublishSubject.create();
#Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
#BeforeClass
public static void setUpRxSchedulers() {
Scheduler immediate = new Scheduler() {
#Override
public Disposable scheduleDirect(#NonNull Runnable run, long delay, #NonNull TimeUnit unit) {
return super.scheduleDirect(run, 0, unit);
}
#Override
public Scheduler.Worker createWorker() {
return new ExecutorScheduler.ExecutorWorker(Runnable::run);
}
};
RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate);
RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate);
RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate);
RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate);
RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate);
}
#Before
#Throws(exceptionClasses = Exception.class)
public void setUp() {
MockitoAnnotations.initMocks(this);
projectsListViewModel = new ProjectsListViewModel(getProjectList);
when(getProjectList.execute()).thenReturn(projectsListPublishSubject.take(1).singleOrError());
}
#Test
public void testExecuteGetProjectsListSuccess() {
LiveData<List<ProjectModel.MapProject>> liveData = projectsListViewModel.getLiveData();
ProjectModel.MapProject expectedResult = new ProjectModel.MapProject(
"testing", "this is a test", "https://logo.jpg",
"cat", "2015-04-04", "active");
projectsListViewModel.getProjects();
projectsListPublishSubject.onNext(projectsList);
Assert.assertEquals(expectedResult, liveData.getValue().get(0));
}
#After
public void tearDownClass(){
RxAndroidPlugins.reset();
}
The code that I have in setUpRxSchedulers is mandatory in order to avoid the same error (Method getMainLooper in android.os.Looper not mocked) with Rx. But I cannot solve this error that I get when calling liveData.post(projectList). In all the forums that I checked they say that with #Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); the problem should be solved. But is not my case.
I put here the viewmodel as well in case it can help:
public class ProjectsListViewModel extends ViewModel {
GetProjectList getProjectList;
MutableLiveData<List<ProjectModel.MapProject>> liveData = new MutableLiveData<>();
public ProjectsListViewModel(GetProjectList getProjectList){
this.getProjectList = getProjectList;
}
public LiveData<List<ProjectModel.MapProject>> getLiveData(){
return liveData;
}
public void getProjects(){
getProjectList.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(ProjectModel.ProjectList::getProjects)
.toObservable().flatMapIterable(projects -> projects)
.map(project -> project.convertToMapProject()).toList()
.subscribe(projectsList ->
liveData.setValue(projectsList));
}
}
The usage of InstantTaskExecutorRule will actually solve this.
I think the issue is that in JUnit 5 the #Rule annotation doesn't seem to be supported anymore (as Extensions are now the way to go). The code will compile successfully, but the rule just won't be applied.
There are (at least) two solutions to this:
Use JUnit 4
Is definitely the quicker, may not be the best depending on how much you need JUnit 5.
This can be done just by changing the annotation of your setup method from #BeforeEach to #Before and by importing the #Test annotation from JUnit 4.
Here's how your imports should look like.
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Implement a InstantTaskExecutorExtension
This is better if you care about using JUnit 5 :)
Here's an article that talks about how to implement precisely InstantTaskExecutorExtension.
Once that's done remember to apply it to your test class using the #ExtendWith(InstantTaskExecutorExtension::class) annotation instead of #Rule!

DaggerMock library -how does it override module?

The DaggerMock library, is used to override dagger modules with fake implementation. Lets take a look at one robolectric topic that is confusing me:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
#Rule public final DaggerMockRule<MyComponent> mockitoRule = new DaggerMockRule<>(MyComponent.class, new MyModule())
.set(new DaggerMockRule.ComponentSetter<MyComponent>() {
#Override public void setComponent(MyComponent component) {
((App) RuntimeEnvironment.application).setComponent(component);
}
});
#Mock RestService restService;
#Mock MyPrinter myPrinter;
#Test
public void testCreateActivity() {
when(restService.doSomething()).thenReturn("abc");
Robolectric.setupActivity(MainActivity.class);
verify(myPrinter).print("ABC");
}
}
So i want to know, with this Rule what exactly is happening ? I can see that RestService was being provided by MyModule but is now being replaced with a mock. But in the examples i don't see a #Inject anywhere so i'm confused how the module was even used in the first place to provide any dependencies ?
I am the author of DaggerMock, thanks for trying it!
The implementation is a bit complicated, the rule create a dynamic subclass of the module (using mockito) and override the provides methods. The rule scans the test fields so it return a field when the module has a method that returns the same type.
The final result is very similar to Mockito InjectMocks annotation. You can take a look at the implementation on github, the core class that override the module is this: https://github.com/fabioCollini/DaggerMock/blob/master/lib/src/main/java/it/cosenonjaviste/daggermock/MockOverrider.java
I release this lib just a week ago, any feedback is welcome!

Categories

Resources