用Mockito进行Android单元测试
我有一个ViewModel,其中有一个方法具有以下代码行:用Mockito进行Android单元测试,android,unit-testing,junit,mockito,android-mvvm,Android,Unit Testing,Junit,Mockito,Android Mvvm,我有一个ViewModel,其中有一个方法具有以下代码行: billDate.set(!TextUtils.isEmpty(SampleApp.getInstance().getAccountManager().getDueDate()) ? String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due), SampleApp
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)
java.lang.NullPointerException
位于java.util.regex.Matcher.getTextLength(Matcher.java:1283)
位于java.util.regex.Matcher.reset(Matcher.java:309)
位于java.util.regex.Matcher.(Matcher.java:229)
位于java.util.regex.Pattern.matcher(Pattern.java:1093)
位于java.util.Formatter.parse(Formatter.java:2547)
位于java.util.Formatter.format(Formatter.java:2501)
位于java.util.Formatter.format(Formatter.java:2455)
位于java.lang.String.format(String.java:2940)
在运行测试用例时,我看到日志显示了一些与模式相关的错误
有人能建议如何测试String.format()
方法吗 首先,您不应该将android视图包导入到ViewModel中。因此,请跳过在ViewModels内部使用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和相关测试代码中的上下文。您不需要测试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
的调用将返回一个模拟对象作为实例,而不是我认为它现在返回的任何真实实例。然后,您可以模拟后续的方法,如getApplicationContext和getString,使它们返回固定的响应,以便string.format
调用不会在空输入时失败
需要注意的是,如果最终使静态getInstance方法返回一个mock,但是确保在测试完成时进行了适当的清理,以将其设置回最初返回的状态,这样就不会无意中修改可能导致另一个不相关的单元测试失败的内容。如果您在单元测试中更改静态方法返回的内容,这始终是一个风险,因为您正在为所有测试有效地更改它。考虑到在使用了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.getApplicationContext()
,则只需确保模拟您随app.getApplicationContext()提供的上下文或SampleApp
本身
doReturn(dueFormatString).when(context).getString(R.string.due);
doReturn(dueMissingString).when(context).getString(R.string.missing_due_date);
但一般来说,您应该将上下文
抽象掉。不使用它将简化您的代码,从而大大简化您的测试
还考虑使用<代码>上下文.GETString()<代码>,而不是<代码> Stry.FrimATA()/<代码>,用于格式化从资源加载的字符串。将格式参数作为参数添加到调用中非常简单
context.getString(R.string.due, am.getBillingDueDate())
你能发布完整的stacktrace吗?@Giorgioantonili我已经粘贴了日志你能添加相关的测试用例吗?getString的默认实现返回null,你可以模拟它或使用包装器接口,如StringProvider。请将测试用例与模拟类@Lavanya一起发布在本例中,是ViewModel类StringProviderImpl吗?