如何 android 单元测试和模拟静态方法

How to android unit test and mock a static method

嗨,我真的希望你能帮助我,我觉得我这几天一直在拔头发。

我正在尝试为方法 A 编写单元测试。方法 A 调用静态方法 B。我想模拟静态方法 B。

我知道以前有人问过这个问题,但我觉得 Android 从那时起已经成熟了,必须有一种方法可以完成这么简单的任务,而无需重写我要测试的方法。

这里有一个例子,首先是我要测试的方法:

public String getUserName(Context context, HelperUtils helper) {
    if(helper == null){
        helper = new HelperUtils();
    }
    int currentUserId = helper.fetchUsernameFromInternet(context);

    if (currentUserId == 1) {
        return "Bob";
    } else {
        return "Unknown";
    }
}

接下来是我要模拟的静态方法:

public class HelperUtils {
    public static int fetchUsernameFromInternet(Context context) {
        int userid = 0;

        Log.i("HelperUtils ", "hello");

        return userid;
    }
}

在其他语言中这很容易,但我无法在 Android 中实现。 我试过 Mockito,但似乎不支持静态方法

HelperUtils helper = Mockito.mock(HelperUtils.class);
Mockito.when(helper.fetchUsernameFromInternet(getContext())).thenReturn(1);

这个错误

org.mockito.exceptions.misusing.MissingMethodInvocationException

我试过 Powermock,但我不确定 Android 是否支持它。我设法在我的 gradle 文件中使用 androidCompile 获得了 powermock 运行 但我收到此错误:

Error:Execution failed for task ':app:dexDebugAndroidTest'. com.android.ide.common.process.ProcessException:

更不用说 PowerMockito.mockStatic(HelperUtils.class); 没有 return 任何东西,所以我不知道要将什么传递到我的 getUsername 方法中!

非常感谢任何帮助。

静态方法与任何对象无关 - 你的 helper.fetchUsernameFromInternet(...)HelperUtils.fetchUsernameFromInternet(...) 相同(但有点令人困惑) - 由于这个 [=12],你甚至应该收到编译器警告=].

此外,您必须使用 @RunWith(...)@PrepareForTest(...)PowerMockito.mockStatic(...) 来代替 Mockito.mock 来模拟静态方法 - 完整示例在这里:PowerMockito mock single static method and return object

换句话说 - 模拟静态方法(以及构造函数)有点棘手。更好的解决方案是:

  • 如果您可以更改 HelperUtils,使该方法成为非静态方法,现在您可以使用通常的 Mockito.mock

    [ 模拟 HelperUtils
  • 如果您不能更改 HelperUtils,请创建一个包装器 class 委托给原始 HelperUtils,但没有 static 方法,然后也使用通常的 Mockito.mock (这个想法有时称为 "don't mock types you don't own")

我是用 PowerMockito 这样做的。

我正在使用AppUtils.class,它包含多个静态方法和函数。

静态函数:

public static boolean isValidEmail(CharSequence target) {
    return target != null && EMAIL_PATTERN.matcher(target).matches();
}

测试用例:

@RunWith(PowerMockRunner.class)
@PrepareForTest({AppUtils.class})
public class AppUtilsTest {

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        PowerMockito.mockStatic(AppUtils.class);

        PowerMockito.when(AppUtils.isValidEmail(anyString())).thenCallRealMethod();
    }

    @Test
    public void testValidEmail() {
        assertTrue(AppUtils.isValidEmail("name@email.com"));
    }

    @Test
    public void testInvalidEmail1() {
        assertFalse(AppUtils.isValidEmail("name@email..com"));
    }

    @Test
    public void testInvalidEmail2() {
        assertFalse(AppUtils.isValidEmail("@email.com"));
    }
}

编辑 1:

添加以下导入:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

希望对您有所帮助。

您可以使用 mockito 最新版本,即 3.4.+,它允许静态模拟

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48

以下是一些解决方案:

  • 将你的静态方法修改为non-static方法或者用non-static方法包装你的静态方法,然后直接使用Mockito mock non-static方法中的[=26] =] 测试.
  • 如果你不想改变任何原始代码,你可以尝试在Android测试中使用dexmaker-mockito-inline-extended模拟静态方法和final方法。我成功地用它模拟了静态方法。检查这个 solution.
  • 在单元测试中使用Robolectric,然后在单元测试中使用PowerMock模拟静态方法。