【问题标题】:Mockito: How do I use getString with mockito?Mockito:如何将 getString 与 mockito 一起使用?
【发布时间】:2016-07-10 20:40:52
【问题描述】:

我从 google 的示例中获得了关于如何通过创建 SharedPreferencesHelper 类来测试您的 SharedPreferences 代码的灵感:

https://github.com/googlesamples/android-https://github.com/googlesamples/android-testing/blob/master/unit/BasicSample/app/src/main/java/com/example/android/testing/unittesting/BasicSample/SharedPreferencesHelper.java

您可以看到该类使用类中硬编码的实际字符串作为sharedPreferences 的键 - 这是该类的摘录:

public class SharedPreferencesHelper {

    // Keys for saving values in SharedPreferences.
    static final String KEY_NAME = "key_name";
    static final String KEY_DOB = "key_dob_millis";
    static final String KEY_EMAIL = "key_email";

    public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
        // Start a SharedPreferences transaction.
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
        editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
        editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());

        // Commit changes to SharedPreferences.
        return editor.commit();
    }

在这里使用他们的SharedPreferencesHelperTest 类进行测试时,他们使用上述类中定义的相同变量访问模拟的sharedPreferences

https://github.com/googlesamples/android-testing/blob/master/unit/BasicSample/app/src/test/java/com/example/android/testing/unittesting/BasicSample/SharedPreferencesHelperTest.java

该类的摘录如下所示:

when(mMockSharedPreferences.getString(eq(SharedPreferencesHelper.KEY_NAME), anyString()))
            .thenReturn(mSharedPreferenceEntry.getName());
    when(mMockSharedPreferences.getString(eq(SharedPreferencesHelper.KEY_EMAIL), anyString()))
            .thenReturn(mSharedPreferenceEntry.getEmail());
    when(mMockSharedPreferences.getLong(eq(SharedPreferencesHelper.KEY_DOB), anyLong()))
            .thenReturn(mSharedPreferenceEntry.getDateOfBirth().getTimeInMillis());

谷歌这样做的方式允许他们绕过使用contextstring.xml资源文件中提取字符串并在sharedPreferences中查询它的问题,就像它是如何一样通常假设使用getString() 发生,即:

mSharedPreferences.getString(context.getString(R.string.name),"");

但是,我读到不能使用context.getString,因为它是一种最终方法,而mockito 不能模拟最终方法:

Mockito - Overriding a method that takes primitive parameters

然后如何使用 mockito 对 getString 的任何方法进行单元测试?任何带有getString 的方法都不起作用,我的单元测试将失败。

这是我为SharedPreferences 编写的课程,我想用sharedPreferencesgetStrings 编写的键对其进行测试:

public class SharedPreferencesHelper {

    // The injected SharedPreferences implementation to use for persistence.
    private final SharedPreferences mSharedPreferences;
    private Context context;

    public SharedPreferencesHelper(SharedPreferences sharedPreferences, Context context) {
        mSharedPreferences = sharedPreferences;
        this.context = context;
    }

    public boolean saveName(String name) {
        // Start a SharedPreferences transaction.
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(context.getString(R.string.name), name);
        return editor.commit();
    }

    public String fetchName() {
        // Start a SharedPreferences transaction.
        return mSharedPreferences.getString(context.getString(R.string.name),"");
    }

    public boolean saveGender(String gender) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(context.getString(R.string.gender), gender);
        return editor.commit();
    }

}

这是我为上述课程编写的测试 - 它与谷歌的 SharedPreferencesHelperTest 非常相似:

@RunWith(MockitoJUnitRunner.class)
public class SharedPreferencesHelperTest {

    private static final String TEST_NAME = "Test name";

    private SharedPreferencesHelper mMockSharedPreferencesHelper;

    @Mock
    SharedPreferences mMockSharedPreferences;
    @Mock
    SharedPreferences.Editor mMockEditor;
    @Mock
    MockContext context;

    @Before
    public void setUp() throws Exception {
        // Create a mocked SharedPreferences.
        mMockSharedPreferencesHelper = createMockSharedPreference();
    }

    @Test
    public void testSaveName() throws Exception {
        boolean success = mMockSharedPreferencesHelper.saveName(TEST_NAME);
        Timber.e("success " + success);
        assertThat("Checking that name was saved... returns true = " + success,
                success, is(true));
        String name = mMockSharedPreferencesHelper.fetchName();
        Timber.e("name " + name);
        assertThat("Checking that name has been persisted and read correctly " + name,
                TEST_NAME,
                is(name));
    }

    /**
     * Creates a mocked SharedPreferences.
     */
    private SharedPreferencesHelper createMockSharedPreference() {
        // Mocking reading the SharedPreferences as if mMockSharedPreferences was previously written
        // correctly.
        when(mMockSharedPreferences.getString(Matchers.eq("name"), anyString()))
                .thenReturn(TEST_NAME);
        when(mMockSharedPreferences.getString(Matchers.eq("gender"), anyString()))
                .thenReturn("M");
        // Mocking a successful commit.
        when(mMockEditor.commit()).thenReturn(true);
        // Return the MockEditor when requesting it.
        when(mMockSharedPreferences.edit()).thenReturn(mMockEditor);
        return new SharedPreferencesHelper(mMockSharedPreferences, context);
    }
}

运行测试失败,因为我在 SharedPreferencesHelper 类中使用了 getString。如果我对密钥进行硬编码,则不会出现错误,即:

public String fetchName() {
    // Start a SharedPreferences transaction.
    return mSharedPreferences.getString("name","");
}

不应在代码中硬编码字符串,那么我该如何解决这个难题?

【问题讨论】:

    标签: android unit-testing mockito android-context


    【解决方案1】:

    您可以通过不使用Context 来获取字符串资源,而是使用Resources 类来解决此问题。 Resources 具有相同的 getString 方法,但不是最终的。

    https://developer.android.com/reference/android/content/res/Resources.html#getString(int)

    您也可以尝试使用 Robolectric,它可以解决这个问题,因为它们实现了上下文。

    http://robolectric.org/

    【讨论】:

      【解决方案2】:

      2017.06 答案:

      我按照 jbarat 的建议使用 Robolectric。入门有点陡峭,因为事情会发生变化/弃用/删除/与每个版本的 Robolectric/Android Studio 不兼容。

      以下是我使用 Android Studio 2.3.3 和 Robolectric 3.3.2 所做的工作:

      build.gradle:

      testCompile "org.robolectric:robolectric:3.3.2"
      testCompile "org.robolectric:shadows-multidex:3.3.2"
      

      MyClass.java:

      public static String myGetString(Context context) {
          return context.getString(R.string.my_string);
      }
      

      测试:

      @RunWith(RobolectricTestRunner.class)
      @Config(constants = BuildConfig.class, packageName = "com.myapplication", sdk = 19)
      public class MyTest {
          private Context mockContext;
      
          @Before
          public void setUp() {
              // inject context provided by Robolectric
              mockContext = RuntimeEnvironment.application.getApplicationContext();
          }
      
          @Test
          public void test_myGetString() {
              assertEquals("MY STRING", MyClass.myGetString(mockContext));
          }
      }
      

      【讨论】:

        【解决方案3】:

        要启用最终方法的模拟,您需要 Mockito v2 并按照此处的说明进行操作 Mock the unmockable: opt-in mocking of final classes/methods

        src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 中创建一个包含一行的文件:

        mock-maker-inline
        

        一旦你这样做了,你就可以模拟 getString() when(mMockContext.getString(R.string.string_name)).thenReturn("Mocked String");

        【讨论】:

        • 这似乎不适用于Android,getString 总是返回null(使用Gradle 3.3.2 和Mockito 2.23.4 测试)。这可以解释原因(他们建议为 Android 制作 Mockito 变体):medium.com/androiddevelopers/…
        猜你喜欢
        • 1970-01-01
        • 2014-07-08
        • 2020-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-10
        • 2022-07-21
        • 1970-01-01
        相关资源
        最近更新 更多