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
Related
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
[UPDATE]:
I know what NPE is,but I don't know why it appears here.So I think this is totally not a duplicated question as What is a Null Pointer Exception, and how do I fix it?.But any way I have found the answer.To use Mockito in instrumented test,addition dependencies dexmaker and dexmaker-mockito are also required:
androidTestCompile "com.google.dexmaker:dexmaker:1.2"
androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"
And if you don't run yout test under MockitoJUnitRunner,addition initialization is also required as below answer has mentioned:
MockitoAnnotations.initMocks(this);
See also Initialising mock objects - MockIto for futher discussion.
I want to write a simple test that checks if user's data is shown on the UI.The Activity retrieves the data stored in sharedPreferences within onResume() and shows it on the UI.The following is my code for the test:
#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
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
public String getName() {
return sharedPreferences.getString(context.getString(R.string.pref_name), "Guest");
}
But when I run the test,it keeps showing NullPointerExceptiions on this line
when(userPreference.getName()).thenReturn(FAKE_NAME);
I've searched for related topics but I still can't see why.I think the concept of mock is to re-define a method's behavior no matter what the real implementation is. I am new to testing,so I am sorry in advance if this is a silly qustion.
By the way the test runs perfectly with the following code
#RunWith(AndroidJUnit4.class)
public class EditProfileActivityTest {
private UserPreference userPreference;
private String FAKE_NAME = "Test";
#Rule
public ActivityTestRule<EditProfileActivity> activityTestRule = new ActivityTestRule(EditProfileActivity.class,true,false);
#Before
public void setUp(){
//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)));
}
}
But the preference data it retrieves is from the "real" device.In this case i can't make an assertion about what will be displayed so I can't tell whether the test is passed.This is why I want to mock the preference to make it predictable.
You have to init your mocks in #Before like so:
public void setUp() {
MockitoAnnotations.initMocks(this);
// ...
}
Your userPreference object is null, but you're trying to call a method on it. If you post all of your code it will be easier.
The idea of a Mock object is correct - but you're not using a Mock object, you're calling when() on a real object, but which hasn't been created yet, thus the NPE.
I'm programming an app with that Nuance SpeechKit that performs TTS. It requires the context (called from getApplicationContext()) to be passed into some functions. Unfortunately, I'm getting this error in my log: ANDROID_CONTEXT parameter is not passed in!!!
Let me give more background: There in a main activity, and it opens a dialog from a button. The dialog invokes the text-to-speech functionality. As a result, I call getApplicationContext() in the main activity and pass it to my DialogFragment as a parameter using setters. Unfortunately, I'm getting this error even though I am calling the setters. So what could be going wrong? Here's a bit of code:
In my main activity:
// Instance variables...
private SpeechKit speechKit;
private Context context;
protected void onCreate(Bundle savedInstanceState) {
...
context = getApplicationContext();
...
this.speechKit = SpeechKit.initialize(context,
"CORRECT_API_KEY",
"sslsandbox.nmdp.nuancemobility.net",
443,
true,
SpeechKitAPIKey);
speechKit.connect();
}
public void invokeDialog() {
...
dialogueFragment.setContext(context);
dialogueFragment.setSpeechKit(speechKit);
...
}
And here's my code for the dialog fragment:
public void setSpeechKit(SpeechKit speechKit) {
this.speechKit = speechKit;
}
private SpeechKit speechKit;
private Context context;
public void setSpeechKit(SpeechKit speechKit) {
this.speechKit = speechKit;
}
public void setContext(Context context) {
this.context = context;
}
// Called when a button is pushed...
public void narrateText(String voice, String phrase) {
Vocalizer vocalizer = speechKit.createVocalizerWithVoice(voice, this, handler);
vocalizer.speakString(phrase, context);
}
Now I have no idea why this error is called. The code compiles fine. Any suggestions please?
I had the same issue, turned out to be that my free account expired (it lasts 90 days).
You probably send the login that is not valid anymore.
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.
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.