【问题标题】:Mock field in Android Activity in Robolectric testRobolectric 测试中 Android Activity 中的模拟字段
【发布时间】:2019-04-05 09:29:50
【问题描述】:

所以,我正在处理遗留代码,我想对更简单的屏幕之一进行单元测试,即登录。 我没有任何 DI 框架。

我使用 robolectric 4.0.1 和 mockito 1.10.19

目前,由于我试图模拟的对象上的 NullPointer,我的测试失败:

测试类

@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);
    }
}

待测类

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

根据谷歌的发现,我主要尝试重新排序我的 TestClass 中的说明,但似乎没有任何效果。 据我了解,调用getFireBaseInstanceId 方法时firebaseInstanceId 不应为null,因为它在setup() 方法期间注入了initMocks(this)。

这适用于其他测试,但这些测试都没有真正结合 robolectric 和 mockito。

编辑1:
我已将 initMocks(this) 作为 setup() 的第一行
我添加了一些 System.out.println 来查看会发生什么,并且我注意到 LoginActivity 构造函数被多次调用(一次用于 initMocks,一次用于 Robolectric.buildActivity)

编辑2: 我已将测试类中的设置更改为如下

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();
    }

虽然可能不像我想要的那样干净,但它可以完成工作而无需更改我的生产代码。

【问题讨论】:

    标签: java android unit-testing mockito robolectric


    【解决方案1】:

    在这种情况下,注入和自动创建将不起作用。这个:

    @InjectMocks
    private LoginActivity activity;
    

    在设置中被以下方式覆盖:

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

    所以你需要手动设置那个对象的FirebaseInstanceId实例变量:

    @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();
    

    【讨论】:

    • 然后我仍然得到“默认 FirebaseApp 未在此过程中初始化 null。请务必先调用 FirebaseApp.initializeApp(Context)。”这是一个非法状态异常,因为在 getFirebaseInstanceId 调用中 firebaseInstanceId 为 null
    • 所以在 Robolectric.buildActivity 期间抛出?
    • 这可能会奏效,我会尝试一下,但这意味着我只是为了单元测试而更改我的生产代码,这让我很不爽,尤其是当它现在停止时firebase 的东西,但以后可能会挂在其他地方。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-03
    • 2013-08-18
    • 1970-01-01
    相关资源
    最近更新 更多