Robolectric Testing activity with dagger2 - android

I have a activity that extend DaggerAppCompatActivity to enable injections on it using new dagger android injection tools
I'm trying test this activity using Robolectric but the test throws
java.lang.RuntimeException: android.app.Application does not implement dagger.android.HasActivityInjector
How to disable dagger 2 injection to test the activity as normal activity
the test code
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class)
public class RegisterActivityTest {
AppCompatActivity activity;
#Before
public void setupActivity() {
activity = Robolectric.setupActivity(RegisterActivity.class);
}
#Test
public void clickingNewAccountText_MakeNewAccountShouldBeVisible() {
TextView registerNewAccountTextView = activity.findViewById(R.id.register_sign_up_textView);
registerNewAccountTextView.performClick();
Button registerNewAccountButton = activity.findViewById(R.id.register_sign_up_button);
assertThat(registerNewAccountButton.getVisibility(), is(View.VISIBLE));
}
}
any idea how to solve this problem

I was having a similar problem earlier. The error you posted appears to indicate that your activity is calling AndroidInjection.inject(this).
Are you defining your own Application class that implements HasActivityInjector?
According to Robolectric's documentation
Robolectric will attempt to create an instance of your Application
class as specified in the manifest
In my case what worked was configuring the unit tests to include the Android resources:
testOptions{
unitTests{
includeAndroidResources true
}
}
in my module build.gradle, under the android{} section.

Related

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();

Firebase Analytics initialise for unit test (Mockito) in Android

I am running a unit test with
#RunWith(MockitoJUnitRunner.class)
I have used firebase Analytics to log events
MyApplication.getAnalytics().getInstance(appContext).logEvent(eventType, bundle)
and this in my Application class
public static FirebaseAnalytics getAnalytics() {
return FirebaseAnalytics.getInstance(appContext);
}
Now while running tests, I am getting NullPointerException. What will be the right way to initialize Analytics for my unit tests or just ignore them.
I am not getting the context in case I try to initialize it in my setup method of tests.
You can create a mock application class that extends your application class and then overrides getAnalytics with a stubbed value or mock object. Also you should make your getAnalytics method non-static as it's easier for testing and you can pass the reference via dependency injection or you can use a static reference to the application class (but that isn't very testable so I would choose the first option)
public class MockApplication extends MyApplication {
public FirebaseAnalytics getAnalytics() {
return mock(FirebaseAnalytics.class)
}
}
Then you can use the #Config annotation to configure your test runner like this
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.M, application = MockApplication.class)
Check this link out https://github.com/robolectric/robolectric/wiki/Using-PowerMock
Refactor yours like this:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class)
#PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })
#PrepareForTest(FirebaseAnalytics.class)
public class TestClass {
#Rule
public PowerMockRule rule = new PowerMockRule();
private FirebaseAnalytics firebase;
#Test
public void testMocking() {
firebase = PowerMockito.mock(FirebaseAnalytics.class);
Context context = PowerMockito.mock(Context.class);
PowerMockito.mockStatic(FirebaseAnalytics.class);
Mockito.when(FirebaseAnalytics.getInstance(context)).thenReturn(firebase);
}
}

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!

Robolectric ShadowActivity error

I am starting to get familiar with Robolectric to create unit tests for Android applications.
My initial test:
#RunWith(RobolectricTestRunner.class)
public class MainActivityTest {
private MainActivity mainActivity;
#Before
public void setUp() throws Exception {
mainActivity = Robolectric.buildActivity(MainActivity.class).create().get();
mainActivity.onCreate(null);
}
#Test
public void sample() throws Exception {
ShadowActivity act = Robolectric.shadowOf(mainActivity);
}
}
But Robolectric.shadowOf(mainActivity) gives me an error:
The type android.animation.Animator cannot be resolved. It is indirectly referenced from required .class files
Any ideas why? I have created simple android app with Android API Level 8. For the tests, I have Robolectric 2.1 jar
changet from shadowOf(mainActivity) to shadowOf_(mainActivity) and everything works fine!

Android functional testing with Dagger

I’m trying to test an Activity with Mockito & Dagger. I have been able to inject dependencies to Activity in my application but when testing the Activity, I have not been able to inject mock to the Activity. Should I inject Activity to test or let getActivity() create it?
public class MainActivityTest extends
ActivityInstrumentationTestCase2<MainActivity> {
#Inject Engine engineMock;
private MainActivity mActivity;
private Button mLogoutBtn;
public MainActivityTest() {
super(MainActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
// Inject engineMock to test
ObjectGraph.create(new TestModule()).inject(this);
}
#Override
protected void tearDown() {
if (mActivity != null)
mActivity.finish();
}
#Module(
includes = MainModule.class,
entryPoints = MainActivityTest.class,
overrides = true
)
static class TestModule {
#Provides
#Singleton
Engine provideEngine() {
return mock(Engine.class);
}
}
#UiThreadTest
public void testLogoutButton() {
when(engineMock.isLoggedIn()).thenReturn(true);
mActivity = getActivity();
mLogoutBtn = (Button) mActivity.findViewById(R.id.logoutButton);
// how to inject engineMock to Activity under test?
ObjectGraph.create(new TestModule()).inject(this.mActivity);
assertTrue(mLogoutBtn.isEnabled() == true);
}
}
I use Mockito and Dagger for functional testing.
The key concept is that your test class inherits from ActivityUnitTestCase, instead of ActivityInstrumentationTestCase2; the latter super-class call onStart() life-cycle method of Activity blocking you for inject your test doubles dependencies, but with first super-class you can handle the life-cycle more fine-grained.
You can see my working examples using dagger-1.0.0 and mockito for test Activities and Fragments in:
https://github.com/IIIRepublica/android-civicrm-test
The project under test is in:
https://github.com/IIIRepublica/android-civicrm
Hope this helps you
I did some more experimenting and found out that Dagger is not able to create activity correctly when it is injected to test. In the new version of test, testDoSomethingCalledOnEngine passes but onCreate is not called on the MainActivity. The second test, testDoSomethingUI fails and there are actually two instances of MainActivity, onCreate gets called to the other instance (created by ActivityInstrumentationTestCase2 I quess) but not to the other. Maybe the developers at Square only thought about testing Activites with Robolectric instead of Android instrumentation test?
public class MainActivityTest extends
ActivityInstrumentationTestCase2<MainActivity> {
#Inject Engine engineMock;
#Inject MainActivity mActivity;
public MainActivityTest() {
super(MainActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
// Inject engineMock to test & Activity under test
ObjectGraph.create(new TestModule()).inject(this);
}
#Module(
includes = MainModule.class,
entryPoints = MainActivityTest.class,
overrides = true
)
static class TestModule {
#Provides
#Singleton
Engine provideEngine() {
return mock(Engine.class);
}
}
public void testDoSomethingCalledOnEngine() {
when(engineMock.isLoggedIn()).thenReturn(true);
mActivity.onSomethingHappened();
verify(engineMock).doSomething();
}
#UiThreadTest
public void testDoSomethingUI() {
when(engineMock.isLoggedIn()).thenReturn(true);
mActivity.onSomethingHappened();
Button btn = (Button) mActivity.findViewById(R.id.logoutButton);
String btnText = btn.getText().toString();
assertTrue(btnText.equals("Log out"));
}
}
I have put everything together and made demo app that shows how to test with dagger: https://github.com/vovkab/dagger-unit-test
Here is my pervious answer with more details:
https://stackoverflow.com/a/24393265/369348

Categories

Resources