2

I have read this post: Is there a way in JMockit to call the original method from a mocked method?

but the recommend solution throws a NPE. Here is my source

 static Map<String, Boolean> detectDeadlocks(int timeInSeconds) {
    final Map<String, Boolean> deadlockMap = new LinkedHashMap<>();
    new Timer().schedule(new TimerTask() {

      @Override
      public void run() {
        // I want to check if the method is run.
        **deadlockMap.put("deadlock", isDeadlockAfterPeriod());**
      }

    }, timeInSeconds * 1000);
    return deadlockMap;
  }

I want to be able to invoke isDeadlockAfterPeriod in my unit test. This is a static method that I have mocked in my unit tests.

My unit test code

  @Test
  public void testDetectDeadlocks() throws Exception {
    new Expectations(){
      {
           // Called from inside the TimerTask.
           ClassUnderTest.isDeadlockAfterPeriod();
           result = false;
      }
    };

    TimerMockUp tmu = new TimerMockUp();
    Deadlocks.detectDeadlocks(0);
    Assert.assertEquals(1, tmu.scheduleCount);
  }

  class TimerMockUp extends MockUp<Timer> {
    int scheduleCount = 0;

    @Mock
    public void $init() {}

    @Mock
    public void schedule(Invocation invocation, TimerTask task, long delay) {
      scheduleCount ++;
      invocation.proceed(); // Trying to call the real method, but this throws a NPE.
    }
  }

Error stack trace is seen with JUnit in Eclipse.

java.lang.NullPointerException
    at com.myproject.test.DeadlocksTest$TimerMockUp.schedule(DeadlocksTest.java:78)
    at com.myproject.test.Deadlocks.detectDeadlocks(Deadlocks.java:41)
    at com.myproject.test.DeadlocksTest.testDetectDeadlocks(DeadlocksTest.java:86)
halfer
  • 19,824
  • 17
  • 99
  • 186
Kartik
  • 2,541
  • 2
  • 37
  • 59
  • For static resources, You can use PowerMock API to mocking. – Prathibha Chiranthana Feb 07 '18 at 12:05
  • 1
    Could you add the NPE's stacktrace to have any clue on what is actually `null`. – dpr Feb 07 '18 at 12:06
  • @PrathibhaChiranthana I am using jMockit library. – Kartik Feb 07 '18 at 12:22
  • @dpr Updated post. – Kartik Feb 07 '18 at 12:25
  • Actually I think you method's design is not very testing friendly. I'd create a method that creates the `Runnable` for the `TimerTask`. This way you can easily unit test the `Runnable` without messing around with mocking too much. – dpr Feb 07 '18 at 12:27
  • @Kartik Static Resources can't be accessed through the object mocking. You have to use PowerMock API as per my understanding. https://stackoverflow.com/questions/44054243/powermockito-mock-single-static-method-and-return-object-inside-another-static-m?noredirect=1&lq=1 – Prathibha Chiranthana Feb 07 '18 at 12:27
  • @PrathibhaChiranthana jMockit allows you to test static code. Secondly, if I use Powermock, I can not use code coverage tools like Jacoco because the PowerMock agent messes with the instrumentation agent. – Kartik Feb 07 '18 at 12:29

1 Answers1

1

Your problem is that you are also faking the Timer's constructor (and not only the schedule method).

By doing so, you are preventing the correct initialization of the Timer, and as you are then using its real implementation, it fails to do so.

Specifically (with the sources I have), you are preventing the initialization of its queue field, which is used on its mainLoop() method (the one that will call to your TimerTask.run()).

Also, you need to do partial mocking of Deadlocks class, as I understand that isDeadlockAfterPeriod is also a static method for the said class.

I'll leave you here a working example:

Deadlocks.class

public class Deadlocks {

    public static Map<String, Boolean> detectDeadlocks(int timeInSeconds) {
        final Map<String, Boolean> deadlockMap = new LinkedHashMap<>();

        new Timer()// this will be the real constructor
                .schedule( // this will be first mocked, then really executed
                        new TimerTask() {

            @Override
            public void run() {
                deadlockMap.put("deadlock", isDeadlockAfterPeriod()); // this will put false after the mock is called
            }

        }, timeInSeconds * 1000);

        return deadlockMap;
    }

    public static Boolean isDeadlockAfterPeriod() {
        return true; // this, we will mock it
    }

}

Test class

@RunWith(JMockit.class)
public TestClass{
    @Test
    public void testDetectDeadlocks() throws Exception {
        new Expectations(Deadlocks.class){ // do partial mocking of the Deadlock class
            {
                // Called from inside the TimerTask.
                Deadlocks.isDeadlockAfterPeriod();
                result = false;
            }
        };

        // prepare the fake
        TimerMockUp tmu = new TimerMockUp();

        // execute the code
        Map<String, Boolean> result = Deadlocks.detectDeadlocks(0);

        // assert results
        assertThat(tmu.scheduleCount, is(1));
        assertThat(result.size(), is(1));
        assertThat(result.get("deadlock"), is(false));
    }

    class TimerMockUp extends MockUp<Timer> {
        int scheduleCount = 0;

        @Mock
        public void schedule(Invocation invocation, TimerTask task, long delay) {
            scheduleCount ++;

            invocation.proceed();
        }
    }
}

In general, be very careful when faking constructors, as you may leave the instances in an inconsistent state.

Alfergon
  • 5,463
  • 6
  • 36
  • 56