4

I am using Junit4 and Mockito for writing my test cases. In one of the classes which is under test, there is a function init(), which gets called from the constructor.

void init(){
//Some code 
  Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
      @Override
      public void run() {
        //Some code
      }
    });
}

The following exception is thrown when trying to create constructor of that class.

java.lang.RuntimeException: Method post in android.os.Handler not mocked.

Then I tried to mock post method of the Handler class using the following code

Handler handler = spy(new Handler());
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);

But still I keep on getting the same exception. What should I do to stub the post method of the Handler class ?

thedarkpassenger
  • 7,158
  • 3
  • 37
  • 61

4 Answers4

0

It's hard to say without seeing more of your context, but I see two options.

First, if you have the ability, you can avoid using new for something you are going to need to mock. You can either inject it in the constructor, or inject a factory in the constructor and mock the factory so you get mock Handler's from it. For most cases, something along those lines is the preferred approach.

If that is not practical, you can use PowerMock to construct new objects.

Use PowerMockito.whenNew, e.g.

Handler handler = spy(new Handler());
when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
whenNew(Handler.class).withExpectedArguments(looper).thenReturn(handler);

That code is not tested, but should basically work.

jhericks
  • 5,833
  • 6
  • 40
  • 60
0

You are correct in interpreting "Method not mocked": You are using a no-implementation version of the Android system library, so you need to use mocking here, or you'll need to switch to a library like Robolectric that has Java implementations of classes like Handler for testing.

You will need doReturn for your stub. One of the interesting unintended aspects of when syntax is that it actually calls the method it's stubbing:

when(handler.post(Matchers.any(Runnable.class))).thenReturn(true);
//   calls
//   handler.post(            null            )

And, because spies call the real method by default, you'll actually invoke the problematic method while trying to stub it. Instead, use doReturn to tell Mockito to temporarily deactivate the stub.

doReturn(true).when(handler).post(Matchers.any(Runnable.class));

You'll need to inject the stub into your test. This is a little tricky because you're doing "heavy lifting" in the constructor; you lose out on a post-construction opportunity to swap out your handler. jhericks mentions a PowerMock solution, though I recommend refactoring so you don't do so much on construction, and as a third option you could work around this with a testing overload:

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
      }
    });
  }
}

Side note: Be extra careful that init is private or final, because it's dangerous to call overridable methods from constructors.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
0

try to execute the runnable code block

val handler: Handler = mock(Handler::class.java)

`when`(handler.post(any(Runnable::class.java))).thenAnswer {
       (it.arguments[0] as? Runnable)?.run()
       true
 }
Ahmed Garhy
  • 475
  • 3
  • 11
-1

Got the same error(java.lang.RuntimeException: Method postDelayed in android.os.Handler not mocked), and ended up to resolve the issue by passing the handler into the class constructor and don't even need to stub it anymore.

Sample test class

@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...
    }

}

The sample class to be tested

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()
    }
}
s-hunter
  • 24,172
  • 16
  • 88
  • 130