Reputation: 3268
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.
Upvotes: 0
Views: 3473
Reputation: 26512
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();
Upvotes: 1