How to spy on Activity when using Robolectric - java

I am new on Android and I am playing around with Robolectric for my unit tests.
I am facing the following problem.
I have an activity I want to test.
MainActivity.java
public class MainActivity extends ActionBarActivity
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
private NavigationDrawerFragment mNavigationDrawerFragment;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
#Override
public void onNavigationDrawerItemSelected (int position) {
...
}
}
This is the test class:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class)
public class MainActivityTests {
private ActivityController<MainActivity> controller;
private MainActivity activity;
private MainActivity spy;
#Test
public void onCreate_shouldStartNavigationDrawerFragment () {
controller = Robolectric.buildActivity(MainActivity.class);
activity = controller.get();
assertThat(activity).isNotNull();
spy = spy(activity);
spy.onCreate(null);
verify(spy).onCreate(null);
}
}
But I am getting the following exception:
java.lang.IllegalStateException: System services not available to Activities before onCreate() at line spy.onCreate(null).
I have been googling for hours and I have tried several workarounds (blindly) without any success. May please anyone guide me?

Here's what did the trick for me. I use attach() before getting an activity to spy on. Tested with Robolectric 3.0
private MainActivity spyActivity;
#Before
public void setUp(){
MainActivity activity = Robolectric.buildActivity(MainActivity.class).attach().get();
spyActivity = spy(activity);
spyActivity.onCreate(null);
}

You should be driving the activity lifecycle through Robolectric.
See: http://robolectric.org/activity-lifecycle/
So for your case you could do:
controller = Robolectric.buildActivity(MainActivity.class);
activity = controller.get();
assertThat(activity).isNotNull();
spy = spy(activity);
controller.create();
Note: it usually doesn't make sense to spy on the activity lifecycle when testing with Robolectric, since you're the one driving it, so you're only testing that your own method calls executed.

If interested in using exactly the same controller, and work with a spy of the activity, you could modify the inner class of the controller via Reflection, check this method:
public static <T extends Activity> T getSpy(ActivityController<T> activityController) {
T spy = spy(activityController.get());
ReflectionHelpers.setField(activityController, "component", spy);
return spy;
}
The ReflectionHelper is available in Robolectric (tested on Robolectric 4.2). Then it is initialized like this:
controller = Robolectric.buildActivity(MainActivity.class);
activity = getSpy(controller.get());
Hope this helps.

It means that you have to first call the onCreate() method. It has to be the very first called method.

Related

Cannot use Toothpick.inject in Fragment

I'm getting strange error while trying to use Toothpick DI in a fragment:
toothpick.registries.NoFactoryFoundException: No factory could be found for class android.app.Application. Check that the class has either a #Inject annotated constructor or contains #Inject annotated members. If using Registries, check that they are properly setup with annotation processor arguments.
My fragment:
public class ApplicationMenu extends SidebarFragment {
#Inject ApplicationsService applicationsService;
#Inject SectionsService sectionsService;
private EventBus bus = EventBus.getDefault();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toothpick.inject(this, Toothpick.openScope(LauncherActivity.class)); // <- Erroring here
}
...
}
Activity:
public class LauncherActivity extends SidebarActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
Scope scope = Toothpick.openScopes(LauncherApplication.class, LauncherActivity.class);
scope.bindScopeAnnotation(LauncherActivitySingleton.class);
scope.installModules(new LauncherActivityModule(this));
super.onCreate(savedInstanceState);
Toothpick.inject(this, scope);
setContentView(R.layout.activity_launcher);
ButterKnife.bind(this);
...
}
...
}
The strange thing is I get the error only in fragments, all injections in other places (ViewRenderers, Adapters, Services etc) work fine with no problem
I figured that out. I close the application scope in one of my services by mistake. That service was using in fragments which cases the error.
Unfortunately the error message does not provide any useful information and the only way to find the problem is to analyze and debug all the code.
So using the toothpick di you have to be very careful with scope life-cycle.

Getting a value from parent activity returns null

I have an activity and a fragment working with.
In the activity, i'm initializing the current user like this(manually):
User currentUser;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
currentUser = new User("email", "name", "secondName", "age");
\\.....}
the MainActivity contains a method like this :
public User getCurrentUser() {
return currentUser;
}
now, here is the problem :
in the child fragment i'm calling getCurrentUser from mainActivity like this :
User u = MainActivity.newInstance().getCurrentUser();
MainActivity.newInstance() - defined in mainActivity like this, returning a static instance of activity :
private static MainActivity mainActivityInstance = new MainActivity();
public static MainActivity newInstance() {
return mainActivityInstance;
}
the problem is that getCurrentUser returns a null object reference even though currentUser is initialized in mainActivity with valid data.
So, calling for example
u.getName();
will throw me a NullPointerException
How can i solve this? i feel like i'm freaking out
Thanks.
MainActivity.newInstance() - defined in mainActivity like this, returning a static instance of activity :
Never create an instance of an activity directly yourself.
the problem is that getCurrentUser returns a null object reference even though currentUser is initialized in mainActivity with valid data
Those activity instances are separate Java objects.
From a fragment, call getActivity() to retrieve the activity object that is hosting the fragment. This is covered in any good book or course on Android app development.
As many people said: "You can't instantiate an Activity in a Fragment".
Second: there are many options you can implement in order to get the User in a Fragment. Here are the common options:
1 - Pass the user as a parameter in the fragment:
public static FragmentChild newInstance(User user){
Bundle b = new Bundle();
b.putSerializable("USER_PARAM",user);
FragmentChild fragment = new FragmentChild();
fragment.putArguments(b);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
User user = getArguments().getSerializable("USER_PARAM")
}
public class User implements Serializable {}
2 - Use the method ((MainActivity)getActivity()).getCurrentUser(). It will work but if you want to do it in the right way you should use an interface instead of using the MainActivity directly.
Interface option:
public class MainActivity extends AppCompatActivity implements UserDeliver {
#Override
public User getUser(){
return user;
}
}
public class MyFragment extends Fragment {
private UserDeliver userDeliver;
private User user;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
user = userDeliver.getUser();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
userDeliver = (UserDeliver) context;
}
#Override
public void onDetach() {
super.onDetach();
userDeliver = null;
}
public interface UserDeliver {
User getUser();
}
}
First off- you can't create an Activity via new. It will compile, but not be properly initialized because only the framework can do that.
Secondly- never hold an activity in a static variable. This will leak memory. Lots of memory, because the entire view hierarchy has a reference from the Activity. You will cause OOM errors doing this.
U are generating a newInstance of the MainActivity everytime you do MainActivity.newInstance()
Try to retrieve your user with ((MainActivity)getActivity).getCurrentUser();
Or create the instance of your user in the fragment

How to listen Android ActivityTestRule's beforeActivityLaunched method in an android test

How do I listen ActivityTestRule's beforeActivityLaunched() method in an android test.
My workaround is creating a custom ActivityTestRule and providing a callback on constructor. Is it a bad practice? Same way is it OK to listen for ActivityTestRule constructor method.
Here is my code:
public class CustomActivityTestRule<A extends Activity> extends ActivityTestRule<A> {
public interface onBeforeListener{
void onBefore(String message);
}
private onBeforeListener listener;
public CustomActivityTestRule(Class<A> activityClass, onBeforeListener listener) {
super(activityClass);
}
#Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
listener.onBefore("before activity launch");
}
}
In android test class, I can do something like:
#Rule public CustomActivityTestRule<MainActivity> mainActivityActivityTestRule = new
CustomActivityTestRule<MainActivity>(MainActivity.class, new CustomActivityTestRule.onBeforeListener() {
#Override
public void onBefore(String message) {
//do something before activity starts
}
});
Same way it is able to do something on junit rule instantiating. Is there any other way to listen for junit test rule instantiating?
You can override beforeActivityLaunched without creation of a new class.
I'm using the following in my tests:
#Rule
public ActivityTestRule<MainActivity> mainActivityActivityTestRule = new ActivityTestRule<MainActivity>(MainActivity.class) {
#Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
}
};
You can configure the test rule so it does not start your activity automatically.
#Rule
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<MainActivity>(MainActivity.class, false, false);
In you setup method you can prepare all what you need and then launch the activity.
activityTestRule.launchActivity(null);
See also
JavaDoc ActivityTestRule constructor
JavaDoc launchActivity method

The test still read from the real SharedPreferences

I have made a simple instrumented test to verify that if the data read from the SharedPreferences is displayed properly on the UI.Both data-retrieving and displaying actions are performed in Activity's onResume()method.
But the problem is,even if I've mocked the preference object and defined the fake return value,the activity still read data from the real preference,ignoring when(...).thenReturn(...)statement.Does anyone have any idea?
#RunWith(AndroidJUnit4.class)
public class EditProfileActivityTest {
#Mock
private UserPreference userPreference;
private String FAKE_NAME = "Test";
#Rule
public ActivityTestRule<EditProfileActivity> activityTestRule = new ActivityTestRule(EditProfileActivity.class,true,false);
#Before
public void setUp(){
//Set fake SharedPreferences
MockitoAnnotations.initMocks(this);
when(userPreference.getName()).thenReturn(FAKE_NAME);
//Start Activity
Intent intent = new Intent();
activityTestRule.launchActivity(intent);
}
#Test
public void showUserData() throws Exception{
onView(withId(R.id.name_tv)).check(matches(withText(FAKE_NAME)));
}
}
where UserPreference is a custom class which simply wraps SharedPreference class and contains lots of getters and setters.This is its constructor
public UserPreference(Context context) {
this.context = context;
sharedPreferences = this.context.getSharedPreferences("Pref", Context.MODE_PRIVATE);
prefEditor = sharedPreferences.edit();
}
and one of its getter and setter:
public String getName() {
return sharedPreferences.getString(context.getString(R.string.pref_name), "Guest");
}
public void saveName(String name){
prefEditor.putString(context.getString(R.string.pref_name), name);
prefEditor.apply();
}
[EDIT]
Simplified version of my original Activity:
public class EditProfileActivity extends AppCompatActivity{
//...
private UserPreference userPreference;
//...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userPreference = new UserPreference(this);
setContentView(R.layout.activity_edit_profile);
//...
}
#Override
protected void onResume() {
super.onResume();
//...
String name = userPreference.getName();
nameEdt.setText(name); //Display the name on an EditText
//...
}
}
The UserPreference mock is created, but the activity still uses the one created in its onCreate method. You need to set that activity's userPreference field to your mock.
There are a few ways to do that:
Add a setter method for the userPreference field and call it in your #Before method:
#Before
public void setUp(){
...
EditProfileActivity activity = activityTestRule.launchActivity(intent);
activity.setUserPreference(mockedUserPreference);
}
This is simple but ugly: you alter the activity solely to accomodate the test.
Set the userPreference field via reflection:
#Before
public void setUp(){
...
EditProfileActivity activity = activityTestRule.launchActivity(intent);
Field userPreferenceField = activity.getClass().getDeclaredField("userPreference");
field.setAccessible(true);
userPreferenceField.set(activity, mockedUserPreference);
}
This is a brittle test: changing the field name breaks it without compile error. The activity doesnt have to be altered, though, so it is useful when you cant change it.
Don't instantiate the UserPreference in the onCreate method. In plain Java i'd add it as a constructor argument, but i don't know if that works as easily with Android. Maybe use a dependency injection framework, they're perfect to use with mocking: Android and Dependency Injection

Getting Null pointer exception while testing class Edit text field in android

This is the class of which I want to test EditText. But when I try to assign that EditText field in the Test clas it shows an null pointer exception. I have omitted other use less code for the problem
public class LogInActivity extends ActionBarActivity {
private Button signUpButton;
private Button logInButton;
private Intent signUpChoiceIntent;
private Intent homeActivityIntent;
private String username;
private String password;
private EditText usernameTextField;
private EditText passwordTextField;
private HumLogController humLogController;
private String error;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_in);
humLogController = (HumLogController) getIntent().getSerializableExtra("controllerObject");
setIntentAndButton();
}
private void setAndCheckFields(){
/** I want to test this, (view with id:- logInUsernameField ) which is working fine in practice, but not passing the test. When I try to call the same id in Test class with instance of this class, it gives a null pointer exception. */
usernameTextField = (EditText) findViewById(R.id.logInUsernameField);
}
This is the test class where I am testing for the EditText field, but giving a null pointer exception
public class LogInActivityInstrumentTest extends InstrumentationTestCase{
LogInActivity logInActivity;
#Override
protected void setUp() throws Exception{
super.setUp();
logInActivity = new LogInActivity();
}
public void testUsernameTextViewNullTest(){
// The line below is line 23. Which is giving null pointer Exception...?
EditText text = (EditText) logInActivity.findViewById(R.id.logInUsernameField);
assertNotNull(text);
}
#Override
protected void tearDown() throws Exception{
super.tearDown();
}
}
The log cat is given below.
java.lang.NullPointerException
at android.app.Activity.findViewById(Activity.java:1853)
at com.example.praduman.humlog.tests.LogInActivityInstrumentTest.testUsernameTextViewNullTest(LogInActivityInstrumentTest.java:23)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:554)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701)
You cannot create an Activity simply calling its constructor as you did. In a test context, you need some kind of instrumentation to allow everything to work properly. Try to take a look at Espresso (for in device tests) or even Robolectric (for JVM tests).
You can not create an instance of your activity using the constructor like ou have done in the above code.
Try changing the following in your LogInActivityInstrumentTest
public class LogInActivityInstrumentTest extends InstrumentationTestCase<LogInActivity>{
public LogInActivityInstrumentTest() {
super(LogInActivity.class);
}
#Override
protected void setUp() throws Exception{
super.setUp();
logInActivity = new getActivity();
}
}
The documentation for running tests can be found here, on developer.android.com.
In the code you have posted - simple creating the Activty using its constructor does not run it through the lifecycle that an activity expects. The reason for your NullPointerException is the fact that onCreate has not been run, meaning that you are trying to look up a view before you have called setContentView(), therefore, the view really is null.

Categories