Why it keeps throwing NPE on mock object - java

[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.

Related

Creating new SpannableStringBuilder returns null

Here is my unit test. Somehow when I create instantiate SpannableStringBuilder instance with a non-null string argument, it appears to be null. What's wrong with this code?
public class CardNumberTextWatcherTest {
private CardNumberTextWatcher watcher;
#Before
public void setUp() throws Exception {
watcher = new CardNumberTextWatcher();
}
#Test
public void afterTextChanged_cardNumberDividedWithSpaces() {
assertTransformText("1111222233334444", "1111 2222 3333 4444");
}
private void assertTransformText(final String testStr,
final String expectedStr) {
final CardNumberTextWatcher watcher = new CardNumberTextWatcher();
final Editable actualStr = new SpannableStringBuilder(testStr);
// When I debug I see that actualStr value is null. Therefore test
//doesn't pass. What's the problem?
watcher.transformText(actualStr);
assertEquals(expectedStr, actualStr.toString());
}
}
The problem was in the testing directory. As soon as SpannableStringBuilder is a part of Android SDK, it couldn't be resolved in regular JUnit test directory. When I moved my class to androidTest directory, everything started to work as expected.
#RunWith(RobolectricTestRunner::class)
Using Robolectric resolves the issue.
You are calling actualStr.toString() which is what the assertion is comparing with, not actualStr itself so look for routes through SpannableStringBuilder to see what could result in null from toString(). Probably testStr is null.

Android: JUnit + Mockito, test callback?

I have an Android app that I'm working on and trying to write unit tests for it. The app is written with the MVP architecture and I am trying to test the Presenter-class.
Simplified method I'm trying to test looks like this:
public void userPressedButton() {
service.loadData(new Callback<Data>{
#Override
onResponse(Data data) {
view.showData(data);
}
});
}
Now I want to verify that when the userPressedButton method is called view.showData(data) is called.
I have tried several approaches but I can't seem to figure out how to test this.
Any ideas?
Edit: to clarify, I want to write a unit test
Interesting case.
What i would do is to:
1) - Create a concrete class for that particular Callback:
public class MyCallback implements Callback<Data>{
private View view;
public MyCallback(View view){
this.view = view;
}
#Override
onResponse(Data data) {
view.showData(data);
}
}
Now for this class you can write a unit test which would check whether the onResponse method calls the showData method of the view field.
2) Having extacted the implementation to a concrete class, from the perspective of the class which contains the userPressedButton method, it really is not essential what happens inside of the Callback class.
It is important that a concrete implementation of that interface has been passed:
public void userPressedButton() {
service.loadData(new MyCallback(view));
}
and finally the test:
#InjectMocks
MyClass myClass;
#Mock
Service service;
#Captor
ArgumentCaptor argCaptor;
#Before
public void init(){
MockitoAnnotations.initMocks(this);
}
#Test
public void shouldUseMyCallback(){
// Arrange
// set up myClass for test
// Act
myClass.userPressedButton();
Mockito.verify(service).loadData(argCaptor.capture());
// Assert
assertTrue(argCaptor.getValue instance of MyCallback);
}
So we check whether the loadData method has been called with proper implementation.
Thats how i would test your case.
You could "store" the callback and use a test callback during your test
class YourClass {
private ??? view;
private Callback<Data> callback;
// for testing purposes
protected YouClass(Callback<Data> callback) {
this.callback = callback;
}
public YouClass() {
this(new Callback<Data>{
#Override
onResponse(Data data) {
view.showData(data);
}
});
}
public void userPressedButton() {
service.loadData(this.callback);
}
}
then use some custom callback for your test
Even more simple solution. If this is MVP, you can pass view instance to presenter class. Then test invocation on Mock.
This is what a test method would look like:
MVPView view = mock(MVPView.class);
Presenter presenter = new Presenter(view)
presenter.userPressedButton();
verify(view, atLeastOnce()).showData(any(Data.class));
If the call is asynchronious, then wait for the result, by modifying the last statement:
verify(view, timetout(5000).atLeastOnce()).showData(any(Data.class));

Unable to find Textview in testing

I am unable to find Textview by id in test. What I do wrong?
private MyActivity myActivity;
#Before
public void setUp() throws Exception {
myActivity= Mockito.mock(MyActivity .class);
}
Test:
#Test
public void testFindView() throws Exception {
System.out.println(myActivity); // This is not null
this.myActivity.setContentView(R.layout.container);
TextView viewText = (TextView) this.myActivity.findViewById(R.id.container_text);
System.out.println(viewText ); // This is null
}
Calling Mockito.mock() doesn't create a real instance, but only an artificial one. It's main purpose is to keep unit tests away from any external dependencies and track interactions with an object.
So when you call this.myActivity.setContentView(R.layout.container); nothing really happens, because mocked myActivity doesn't have the insides of a regular MyActivity - you're only calling a stub method that you have not ordered to do anything.
So you need to create a real instance of MyActivity if you want to test how it works. You can also play with Spy objects if you still want to track interactions (you can check them out here)

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 a Context for use in AndroidTestCase when class under test is not an activity

I am trying to test using AndroidTestCase. I am trying to test only one particular class in my application, however this class does not extend Activity, Service or anything else. It is basically a plain Java class apart from the fact that it requires a Context. It is a pojo and some of its variables are objects that require android api calls in their creation, e.g. a call to the SensorManager.
I tried to use:
Context context = getContext();
When running my tests this gives me the exception "System services not available to activites before onCreate()". Does that method have to be overridden?
final Context context = new IsolatedContext(null, getContext()) gives the same thing.
The reason I am using the Android testing framework and not something like Robolectric is because the class I'm testing gathers hardware information about a device and so I want to run the tests on an actual device. I have looked at the developer docs for AndroidTestCase but can't see what I'm looking for in the examples. I'm not sure the other test case classes will achieve what I want. Any ideas?
My test class:
public class DeviceTest extends AndroidTestCase {
ClassToTest mClassToTest;
#Override
protected void setUp() throws Exception {
final Context context = new IsolatedContext(null, getContext()) {
#Override
public Object getSystemService(final String pName) {
return getContext().getSystemService(pName);
}
};
mClassToTest = new ClassToTest(context);
super.setUp();
}
public void testClassMethod() {
Object mObject;
mObject = mClassToTest.getObject();
assertNotNull(mObject);
}
#Override
protected void tearDown() throws Exception {
mClassToTest = null;
super.tearDown();
}
}
Thanks in advance.
UPDATE: After changing my setup to the following:
#Override
protected void setUp() throws Exception {
super.setUp();
context = this.getContext();
mClassToTest = new ClassToTest(context);
}
I am getting an error that context is null. In what scenarios would AndroidTestCase.getContext() return null? My setup seems to be ok....
From AndroidTestCase you can access directly mContext, or call getContext().
From the context returned by those, you could also call Context.getApplicationContext() if you wanted that one.
You can use mContext from super class (AndroidTestCase). I used it for the testing of the database where context is required.
AndroidTestCase.class
public class AndroidTestCase extends TestCase {
protected Context mContext;
...
}
You would be able to use Context in the inherited class of AndroidTestCase.
TestDb.java
public class TestDb extends AndroidTestCase {
void deleteTheDatabase() {mContext.deleteDatabase(DB_NAME)};
}
There are a few ways around this, you could use a mockcontext as one solution or if you really do not care what the context is just that is valid you can use an InstrumentationTestCase and get the context of the test apk via getInstrumentation().getContext().
I think the reason your context is null is that actually no android context exists at this point, you can get one by creating an application or an activity.

Categories