【问题标题】:Android Unit Testing with Mockito使用 Mockito 进行 Android 单元测试
【发布时间】:2020-02-22 13:39:53
【问题描述】:

我有一个 ViewModel,其中有一个方法具有以下代码行:

billDate.set(!TextUtils.isEmpty(SampleApp.getInstance().getAccountManager().getDueDate()) ?
            String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due),
                    SampleApp.getInstance().getAccountManager().getBillingDueDate()) :
            SampleApp.getInstance().getApplicationContext().getString(R.string.missing_due_date));

我有一个使用 Mockito 的测试类来测试 ViewModel 中的不同方法。但在这一行,NullPointerException 失败了:

String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due),

以下是日志:

java.lang.NullPointerException
at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
at java.util.regex.Matcher.reset(Matcher.java:309)
at java.util.regex.Matcher.<init>(Matcher.java:229)
at java.util.regex.Pattern.matcher(Pattern.java:1093)
at java.util.Formatter.parse(Formatter.java:2547)
at java.util.Formatter.format(Formatter.java:2501)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)

在运行测试用例时,我看到日志显示一些与 Pattern 相关的错误 有人可以建议,如何测试String.format() 方法?

【问题讨论】:

  • 你能发布完整的堆栈跟踪吗?
  • @GiorgioAntonioli 我已经粘贴了日志
  • 可以添加相关的测试用例吗?
  • getString的默认实现返回null,可以mock或者使用StringProvider等封装接口。
  • 请将测试用例与模拟类@Lavanya 一起发布

标签: android unit-testing junit mockito android-mvvm


【解决方案1】:

首先,您不应该将 android 视图包导入您的 ViewModel。所以跳过在 ViewModel 中使用 TextUtils 之类的东西。

至于getApplicationContext().getString(),为此创建一个接口。比如:

interface StringProvider {
    String getString(int resource);
}

然后在您的 ViewModel 构造函数中传递该接口并使用它来获取您想要的字符串。

初始化 ViewModel 时,可以像这样传递StringProvider 的具体实现:

class StringProviderImpl implements StringProvider {
     String getString(int resource) {
        return SampleApp.getInstance().getApplicationContext().getString(resource);
     }
}

这样,对于您的单元测试,您可以只模拟 StringProvider 而不必担心处理 ViewModel 中的上下文和相关的测试代码。

【讨论】:

  • 本例中是ViewModel类StringProviderImpl吗?
【解决方案2】:

您不需要测试String.format 方法。那不是你的代码,你的目标应该是测试你自己的代码。但是您的代码正在使用该方法,因此您需要测试您的代码。据我了解,这是您尝试验证或模拟的部分:

String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due), SampleApp.getInstance().getAccountManager().getBillingDueDate()) 

它会多次调用 SampleApp 来获取实例。由于对 SampleApp.getInstance 的调用是静态方法调用,因此您将无法模拟它们。没有发布足够的代码来了解 SampleApp 是什么或 SampleApp.getInstance() 返回什么,或者知道对该实例的任何后续调用是否返回 null,但其中一个是。所以我认为要解决这个问题,您需要查看 getInstance 方法返回的内容。如果您无法触及该代码并且您希望修改您的测试类,那么由于静态方法,您可能无法使用 mockito 对其进行测试。

但除此之外,您将需要为您的测试构建一种方法,以便对 SampleApp.getInstance 的调用返回一个 mock 对象作为实例,而不是我认为它现在返回的任何真实实例。然后,您可以模拟 getApplicationContext 和 getString 等后续方法,使它们返回预设响应,这样string.format 调用就不会因输入为空而失败。

请注意——如果您最终使静态 getInstance 方法返回一个模拟,但请确保您在测试完成后进行了适当的清理以将其设置回最初返回的内容,这样您就不会无意中修改可能导致另一个不相关的单元测试失败的东西。如果您在单元测试中更改静态方法返回的内容,这总是有风险的,因为您实际上是在为所有测试更改它。

【讨论】:

    【解决方案3】:

    考虑到在 AccountManager 已经使用后测试失败,您应该已经将 SampleApp 设置为模拟或伪造。

    SampleApp app = SampleApp.getInstance()
    AccountManager am = app.getAccountManager();
    Context context = app.getApplicationContext();
    billDate.set(!TextUtils.isEmpty(am.getDueDate()) ?
        String.format(context.getString(R.string.due), am.getBillingDueDate()) :
        context.getString(R.string.missing_due_date);
    

    现在,如果您直接使用 app.getString(),您只需确保模拟您提供的 Contextapp.getApplicationContext()SampleApp 本身。

    doReturn(dueFormatString).when(context).getString(R.string.due);
    doReturn(dueMissingString).when(context).getString(R.string.missing_due_date);
    

    但总的来说,您应该将Context 抽象出来。不使用它会简化你的代码,因此你的测试会很多。

    还可以考虑使用context.getString() 而不是String.format() 来格式化从资源加载的字符串。就像将格式参数作为参数添加到调用中一样简单。

    context.getString(R.string.due, am.getBillingDueDate())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-15
      • 1970-01-01
      • 2017-11-22
      • 2017-09-19
      • 2019-04-27
      相关资源
      最近更新 更多