如何在Android单元测试中模拟和测试静态方法

17

你好,我真的希望你能帮助我,感觉自己已经为此烦恼了好几天。

我正在尝试为方法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); 没有返回任何内容,所以我不知道应该传入什么参数到我的getUsername方法!

任何帮助将非常感激。


我没有一个确切的答案,但是最近我通过使用服务定位器模式替换单例和静态类来解决这个问题。这使得它们易于测试,并且每个类转换只需要几分钟。 - manabreak
3
我对其他语言的测试了解不多,但是在Java中,“static”关键字对于测试来说是个敌人。 - Solomon Slow
你可能想要看一下PowerMock(https://github.com/jayway/powermock)。它是一个测试框架,可以让你测试困难的情况。 - Micho
PowerMockito只是提供了PowerMock的Mockito风格API,从技术上讲它们都做同样的事情。 - iirekm
4个回答

9

静态方法与任何对象都没有关联 - 你的helper.fetchUsernameFromInternet(...)HelperUtils.fetchUsernameFromInternet(...)是一样的(但有点混淆) - 你甚至应该因为这个helper.fetchUsernameFromInternet得到编译器警告。

而且,如果要模拟静态方法,你必须使用:@RunWith(...)@PrepareForTest(...)然后PowerMockito.mockStatic(...) - 完整的例子在这里:PowerMockito mock single static method and return object

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

  • 如果你可以改变HelperUtils,将那个方法变成非静态的,现在你可以用通常的Mockito.mock来模拟HelperUtils

  • 如果你不能改变HelperUtils,创建一个包装类,委托给原始的HelperUtils,但不具有static方法,然后也使用通常的Mockito.mock(这个想法有时被称为“不模拟你不拥有的类型”)


5

我使用了 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;

希望这能对您有所帮助。

@FazalHussain,我已经更新了答案,请您知悉。 - Hiren Patel
当类型不是字符串时该怎么办? - Harish Gyanani
1
@HarishGyanani 你可以使用 any() - Hiren Patel

0

0

以下是一些解决方案:

  • 将您的静态方法修改为非静态方法或使用非静态方法包装您的静态方法,然后直接使用Mockito在Android测试中模拟非静态方法。
  • 如果您不想更改任何原始代码,可以尝试使用dexmaker-mockito-inline-extended来模拟Android测试中的静态方法和final方法。我成功地使用它模拟了静态方法。请查看此solution
  • 在单元测试中使用Robolectric,然后使用PowerMock来模拟静态方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接