我正在使用Junit4
和Mockito
来编写我的测试用例。在其中一个正在测试的类中,有一个函数init()
,它从构造函数中调用。
void init(){
//Some code
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//Some code
}
});
}
尝试创建该类的constructor
时会抛出以下异常。
java.lang.RuntimeException: Method post in android.os.Handler not mocked.
然后我尝试使用以下代码模拟post
类的Handler
方法
Handler handler = spy(new Handler());
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
但我仍然保持同样的exception
。我该怎么做才能存根Handler类的post方法?
答案 0 :(得分:0)
如果不了解更多情况,很难说,但我看到两种选择。
首先,如果你有能力,你可以避免使用Handler
来模拟你需要的东西。您可以在构造函数中注入它,也可以在构造函数中注入工厂并模拟工厂,以便从中获取模拟Handler handler = spy(new Handler());
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
whenNew(Handler.class).withExpectedArguments(looper).thenReturn(handler);
。对于大多数情况,沿着这些方向的东西是首选的方法。
如果不切实际,可以使用PowerMock to construct new objects。
使用PowerMockito.whenNew,例如
<title>Bootstrap Example</title>
<body>
<div class="container">
<div class="form-horizontal">
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">Panel1</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-md-2" for="ResidentStateType">State Type1</label>
<div class="col-md-10">
<select class="form-control" id="ResidentState" name="ResidentState">
<option value=""></option>
<option value="S">Subscriber</option>
<option value="M">Member</option>
</select>
<span class="field-validation-valid text-danger" data-valmsg-for="ResidentStateType" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="ResidentState">State1</label>
<div class="col-md-10">
<select class="form-control" id="ResidentState" name="ResidentState">
<option value=""></option>
<option value="AK">ALASKA</option>
<option value="AL">ALABAMA</option>
<option value="ETC">ARKANSAS</option>
</select>
<span class="field-validation-valid text-danger" data-valmsg-for="ResidentState" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</div>
</div>
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">Situs/Contract State</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-md-2" for="SitusContractStateType">State Type2</label>
<div class="col-md-10">
<select class="form-control" id="SitusContractStateType" name="SitusContractStateType">
<option value=""></option>
<option value="R">Resident</option>
<option value="C">Client</option>
</select>
<span class="field-validation-valid text-danger" data-valmsg-for="SitusContractStateType" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="SitusContractState">State2</label>
<div class="col-md-10">
<select class="form-control" id="SitusContractState" name="SitusContractState">
<option value=""></option>
<option value="AK">ALASKA</option>
<option value="AL">ALABAMA</option>
<option value="ETC">ARKANSAS</option>
</select>
<span class="field-validation-valid text-danger" data-valmsg-for="SitusContractState" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
该代码未经过测试,但基本上应该可以使用。
答案 1 :(得分:0)
您在解释“方法未被模拟”时是正确的:您正在使用a no-implementation version of the Android system library,因此您需要在此处使用模拟,或者您需要切换到具有Java的Robolectric库像Handler
这样的类的实现用于测试。
您的存根需要doReturn
。 when
语法的一个有趣的无意识方面是它实际上调用了它的存根方法:
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
// calls
// handler.post( null )
并且,因为间谍默认调用真实方法,所以实际上在尝试存根时会调用有问题的方法。相反,请使用doReturn
告诉Mockito暂时停用存根。
doReturn(true).when(handler).post(Matchers.any(Runnable.class));
你需要在你的测试中注入存根。这有点棘手,因为你在构造函数中做了“繁重的工作”;你失去了一个建设后的机会来换掉你的经纪人。 jhericks mentions a PowerMock solution,虽然我建议重构,所以你不要在构造上做那么多,作为第三种选择,你可以通过测试过载来解决这个问题:
public class YourClass {
/** Public constructor for external access. */
public YourClass() {
this(new Handler(Looper.getMainLooper()));
}
/** Package-private constructor for testing. */
YourClass(Handler handler) {
init(handler);
}
private void init(Handler handler) {
handler.post(new Runnable() {
@Override public void run() {
//Some code
}
});
}
}
附注:要特别注意init
是私有的还是最终的,因为it's dangerous to call overridable methods from constructors。
答案 2 :(得分:0)
遇到相同的错误( java.lang.RuntimeException:android.os.Handler中的方法postDelayed未模拟),最终通过将处理程序传递给类来解决此问题构造函数,甚至不需要再对其进行存根。
样本测试班
@RunWith(MockitoJUnitRunner::class)
class MainViewModelImplTest {
@Mock
lateinit var handler: Handler
lateinit var mainViewModel: MainViewModel
@Before
fun setUp() {
mainViewModel = MainViewModelImpl(handler = handler)
}
@After
fun tearDown() {
}
@Test
fun testDoStuff() {
mainViewModel.doStuff()
//verifty...
//assert...
}
}
要测试的示例类别
class MainViewModelImpl
@Inject constructor(
val handler: Handler
): ViewModel() {
private val task = object : Runnable {
override fun run() {
// do things
handler.postDelayed(this, 60 * 1000)
}
}
fun doStuff() {
task.run()
}
}