我使用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.
然后,我尝试使用以下代码模拟Handler
类的post
方法
Handler handler = spy(new Handler());
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
但我仍然得到了同样的exception
。我应该做些什么来存根Handler类的post方法?
很难说没有看到更多的上下文,但我看到了两个选项。
首先,如果你有能力,你可以避免使用new
来模拟你需要模拟的东西。您可以将其注入构造函数,也可以在构造函数中注入一个工厂并模拟工厂,这样您就可以从中获得mock Handler
。在大多数情况下,最好采用类似的方法。
如果这不实用,可以使用PowerMock来构造新对象。
使用PowerMockito.whenNew,例如
Handler handler = spy(new Handler());
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
whenNew(Handler.class).withExpectedArguments(looper).thenReturn(handler);
该代码没有经过测试,但基本上应该可以工作。
你对"Method not mocking"的解释是正确的:你使用的是Android系统库的无实现版本,所以你需要在这里使用mocking,或者你需要切换到Robolectric这样的库,该库具有Handler
等类的Java实现来进行测试。
您的存根需要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提到了PowerMock解决方案,尽管我建议进行重构,这样你就不会在构建上做太多,作为第三种选择,你可以通过测试过载来解决这个问题:
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
是私有的或最终的,因为从构造函数中调用可重写的方法是危险的。
尝试执行可运行的代码块
val handler: Handler = mock(Handler::class.java)
`when`(handler.post(any(Runnable::class.java))).thenAnswer {
(it.arguments[0] as? Runnable)?.run()
true
}
得到了相同的错误(java.lang.RuntimeException:Method postDelayed in android.os.Handler not mocke),并最终通过将处理程序传递到类构造函数来解决问题,甚至不需要再存根它。
样品测试等级
@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()
}
}