无法发布android.os.Handler类的post方法

时间:2016-05-16 08:35:08

标签: java junit mockito

我正在使用Junit4Mockito来编写我的测试用例。在其中一个正在测试的类中,有一个函数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方法?

3 个答案:

答案 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()
    }
}
相关问题