Android 如何使用Mockito模拟DisplayMetrics

Android 如何使用Mockito模拟DisplayMetrics,android,unit-testing,mockito,Android,Unit Testing,Mockito,我有一个自定义视图,我正在尝试测试不同的屏幕大小,但我找不到关于如何在单元测试中正确模拟显示宽度的文档或示例 下面是我如何尝试使用此功能的示例: private CustomTextView customTV; @Override protected void setUp() throws Exception { super.setUp(); DisplayMetrics displayMetrics = mock(DisplayMetrics.class); dis

我有一个自定义视图,我正在尝试测试不同的屏幕大小,但我找不到关于如何在单元测试中正确模拟显示宽度的文档或示例

下面是我如何尝试使用此功能的示例:

private CustomTextView customTV;

@Override
protected void setUp() throws Exception {
    super.setUp();

    DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
    displayMetrics.widthPixels = 600;

    Resources mockResources = mock(Resources.class);
    when(mockResources.getDisplayMetrics()).thenReturn(displayMetrics);

    Context mockContext = mock(Context.class);
    when(mockContext.getResources()).thenReturn(mockResources);

    customTV = new CustomTextView(mockContext);
}

@SmallTest
public void testViewSize() {
    Assert.assertEquals("Text size did not scale", 42, customTV.getTextSize());
}
此外,我只在某些设备上遇到了一个错误(上面的实现适用于某些屏幕大小,但不是所有屏幕大小):

testViewSize中的
错误:
java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法“boolean android.content.res.Configuration.isLayoutSizeAtLeast(int)”
在android.view.ViewConfiguration上(ViewConfiguration.java:284)
在android.view.ViewConfiguration.get(ViewConfiguration.java:364)
在android.view.view.(view.java:3781)
在android.view.view.(view.java:3876)
位于android.widget.TextView。(TextView.java:655)
在android.widget.TextView。(TextView.java:650)
位于android.widget.TextView。(TextView.java:646)
在com.foo.bar.view.CustomTextView.(CustomTextView.java:55)
在com.foo.bar.view.CustomTextView.(CustomTextView.java:45)
位于com.foo.bar.view.CustomTextViewTests.setUp(CustomTextViewTests.java:52)
位于android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)
位于android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)
位于android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:557)
位于android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1874)
我真正想做的就是将
displayMetrics.widthPixels
的返回值设置为一个特定的数字,但是因为它不是一个方法调用,就像getter或setter一样,我不能像在其他模拟对象中使用
when
thenReturn
方法那样重写它


有没有更简单的方法来模拟上下文/资源/显示指标?

要回答您的问题,您可以像现在这样模拟它

DisplayMetrics metrics = mock(DisplayMetrics.class);
metrics.widthPixels = 300;

Resources resources = mock(Resources.class);
when(resources.getDisplayMetrics()).thenReturn(metrics);

Assert.assertEquals(300, resources.getDisplayMetrics().widthPixels);
// ^^ see proof
您发布的错误消息只是建议您需要模拟更多对象。你做得不够

java.lang.NullPointerException:尝试调用虚拟方法“boolean android.content.res.Configuration”

因此,还要模拟配置对象

编辑:

使用Robolectric时使用真实配置显示的代码:

Context context = mock(Context.class);
Resources resources = mock(Resources.class);
when(resources.getConfiguration()).thenReturn(RuntimeEnvironment.application.getResources().getConfiguration());
when(context.getResources()).thenReturn(resources);

最终找到了两种不同的方法,这两种方法最多只能使用Mockito作为测试库

选项1:更新真实的
上下文
选项2:使用
ContextWrapper
+
Mockito.spy

Mockito无法模拟/监视final、anonymous或primitive类型,因为它是
final
类,Mockito无法模拟它。我只是想在这里回答您的问题“我真正想做的是将displayMetrics.widthPixels的返回值设置为特定的数字”,上面所做的就是这样。如果需要配置或其他对象,只需在调用该方法时传入一个真实的配置或对象即可。
Context context = mock(Context.class);
Resources resources = mock(Resources.class);
when(resources.getConfiguration()).thenReturn(RuntimeEnvironment.application.getResources().getConfiguration());
when(context.getResources()).thenReturn(resources);
private CustomTextView customTV;

@Override
protected void setUp() throws Exception {
    super.setUp();

    Context context = getContext();
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();

    Configuration config = context.getResources().getConfiguration();
    displayMetrics.widthPixels = 600;
    // can optionally set the screen density as well:
    displayMetrics.densityDpi = DisplayMetrics.DENSITY_MEDIUM;
    config.densityDpi = DisplayMetrics.DENSITY_MEDIUM;

    context.getResources().updateConfiguration(config, displayMetrics);

    customTV = new CustomTextView(mockContext);
}

@SmallTest
public void testViewSize() {
    Assert.assertEquals("Text size did not scale", 42, customTV.getTextSize());
}
private CustomTextView customTV;

@Override
protected void setUp() throws Exception {
    super.setUp();

    Context context = new ScreenWidthContextWrapper(getContext(), 600);

    customTV = new CustomTextView(context);
}

private class ScreenWidthContextWrapper extends ContextWrapper {
    private final DisplayMetrics mDisplayMetrics;
    private final Resources mSpyResources;

    public ScreenWidthContextWrapper(Context realContext, int widthPixels) {
        super(realContext);

        Resources realResources = realContext.getResources();

        mDisplayMetrics = new DisplayMetrics();
        mDisplayMetrics.widthPixels = widthPixels;
        mDisplayMetrics.setTo(realResources.getDisplayMetrics());

        mSpyResources = spy(realResources);
        when(mSpyResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
    }

    @Override
    public Resources getResources() {
        return mSpyResources;
    }
}

@SmallTest
public void testViewSize() {
    Assert.assertEquals("Text size did not scale", 42, customTV.getTextSize());
}