1

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
        //...
    }
}
Tony Chen
  • 207
  • 2
  • 13
  • Are you using the same `userPreference.getName()` statement in your Activity? – Scadge Nov 17 '15 at 06:10
  • Yes I use the same statement in the original Activity – Tony Chen Nov 17 '15 at 06:15
  • @TonyChen can u please show where you are adding values in pref. like you are retriving with key(using strings) return sharedPreferences.getString(context.getString(R.string.pref_name), "Guest"); – user1140237 Nov 17 '15 at 06:23
  • Actually I save the value from another activity using `userPreference.saveName(NAME)`.I have added the setter in my post for reference – Tony Chen Nov 17 '15 at 06:29
  • You've mocked a UserPreference object and defined a fake return value, but i dont see where you inject that mock into the activity that is being tested. MockitoAnnotations.initMocks doesnt do that for you. Where does the `userPreference` object in the activity's onResume method come from? – Bewusstsein Nov 17 '15 at 10:44
  • I apologize for my incomplete description.Now I have added more details on my original Activity.The userPreference is initialized in `onCreate()` method. – Tony Chen Nov 17 '15 at 10:57

1 Answers1

0

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

Community
  • 1
  • 1
Bewusstsein
  • 1,591
  • 13
  • 19
  • Yes this is exactly the problem! I didn't.inject my `mockedPreference` into the Activity.Since either two ways may change the structure of my Activity and test class,I think I will just clear and save some data to the real preference to keep things simple.Thanks for your solutions anyway!! – Tony Chen Nov 17 '15 at 15:41