简体   繁体   中英

Mock field in Android Activity in Robolectric test

So, I'm working in legacy code, and I want to unit test one of the easier screens, namely the login. I don't have any DI framework.

I use robolectric 4.0.1 and mockito 1.10.19

Currently, my test fails due to a NullPointer on an object that I tried to mock:

TestClass

@RunWith(RobolectricTestRunner.class)
public class LoginActivityTest {
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    FirebaseInstanceId firebaseInstanceId;

    @InjectMocks
    private LoginActivity activity;

    @Before
    public void setup() {

        when(firebaseInstanceId.getToken()).thenReturn("mockToken");
        ActivityController<LoginActivity> activityController = Robolectric.buildActivity(LoginActivity.class);
        activity = activityController.get();
        initMocks(this);
        activityController.create();
    }

    @Test
    public void checkThatLoginButtonExists() {
        // does not reach this point
        Button btn = (Button) activity.findViewById(R.id.button_login);

        assertNotNull("Button exisits", btn);
    }
}

class under test

public class LoginActivity extends FragmentActivity {
    private Button mBtnLogin;

    public LoginActivity() {
        //default constructor
    }

    private FirebaseInstanceId firebaseInstanceId;
    private FirebaseInstanceId getFirebaseInstanceId(){
        if (firebaseInstanceId == null){
            firebaseInstanceId = FirebaseInstanceId.getInstance();
        }
        return firebaseInstanceId;
    }

    private void initViews() {
        ...
        mBtnLogin = (Button) findViewById(R.id.button_login);
        ...
    }    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_layout);
        ...
        initViews();
        ...
        registerNotification();
    }

    private void registerNotification() {
        String newFcmToken = getFirebaseInstanceId().getToken(); // fails here
        ...
    }
}

I've tried mainly to reorder the instructions in my TestClass, based on google findings, but nothing seems to work. As far as I understand it, firebaseInstanceId should not be null when the getFireBaseInstanceId method is called since it is injected with initMocks(this) during the setup() method.

This works for other tests, but none of those actually combine robolectric and mockito.

EDIT1:
I've put the initMocks(this) as the first line in setup()
I've added some System.out.println to see what happens, and I've noticed that the LoginActivity constructor gets calles multiple times(once for initMocks, once for Robolectric.buildActivity)

EDIT2: I've changed my setup in my test class to be as follows

private void setMyOwnMock(String fieldName, Object inClass, Object mock ){
        Field declaredField;
        try {
            declaredField = inClass.getClass().getDeclaredField(fieldName);
            declaredField.setAccessible(true);
            declaredField.set(inClass, firebaseInstanceId);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Before
    public void setup() {
        ActivityController<LoginActivity> activityController = Robolectric.buildActivity(LoginActivity.class);
        activity = activityController.get();

        setMyOwnMock("firebaseInstanceId", activity, firebaseInstanceId);

        when(firebaseInstanceId.getToken()).thenReturn("mockToken");

//        System.out.println(activity.firebaseInstanceId == null);

        activityController.create();
    }

while maybe not as clean as I would like, it gets the job done without having to change my production code.

The injection and auto-creation will not work in this case. This:

@InjectMocks
private LoginActivity activity;

is overridden in the set-up by:

ActivityController<LoginActivity> activityController = 
   Robolectric.buildActivity(LoginActivity.class);
activity = activityController.get();

So you need to manually set the FirebaseInstanceId instance variable of that object:

@Before
public void setup() {
        initMocks(this);
        when(firebaseInstanceId.getToken()).thenReturn("mockToken");
        ActivityController<LoginActivity> activityController = 
            Robolectric.buildActivity(LoginActivity.class);
        activity = activityController.get();
        activity.setFirebaseInstanceId(firebaseInstanceId);         
        activityController.create();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM