Question

I'm looking for a way in JMockit to inject the private fields inside a class while maintaining the ability to trigger the real methods. I use @Injectable and @Tested offered by JMockit. But somehow after that the injected instance is not able to call the real method.

Example test:

public class TestClass {
    public static class DoSomething {
        private Call callee;

        public void execute() {
            callee.call();
        }
    }

    public static class Call {
        public void call() {
            System.out.println("real");
        }
    }

    @Tested DoSomething doSomething;
    @Injectable Call call;

    // nothing happens
    @Test
    public void testRealCall() {
        doSomething.execute();
    }

    // invocation doesn't help either
    @Test
    public void testRealCallSecondTry() {
        new MockUp<Call>() {
            @Mock
            @SuppressWarnings("unused")
            public void call(Invocation inv) {
                inv.proceed();
            }
        };
        doSomething.execute();
    }

    // this works, but requires redundant methods
    @Test
    public void testRealCallThirdTry() {
        new MockUp<Call>() {
            @Mock
            @SuppressWarnings("unused")
            public void call() {
                System.out.println("real");
            }
        };
        doSomething.execute();
    }

    @Test
    public void testFakeCall() {
        new MockUp<Call>() {
            @Mock
            @SuppressWarnings("unused")
            public void call() {
                System.out.println("fake");
            }
        };
        doSomething.execute();
    }
}

Here DoSomething wraps the Call instance, which provides a way to print a message. The ideal output of the four test cases would be:

real
real
real
fake

However the actual scenario is that only 3 and 4 worked, printing:

real
fake

This shows if an instance is created using @Injectable. It's not able to directly call the original method without copying and pasting the old method body to the mocked version. That seems really awkward. Is there a workaround of this?

Was it helpful?

Solution

My understanding is that if you use @Injectable you just get an empty mock and then you can no longer call the original method.

The workaround that I would use is to do the injection "manually" like this:

public class TestClass {
  public static class DoSomething {
    private Call callee;

    public void execute() {
      callee.call();
    }
  }

  public static class Call {
    public void call() {
      System.out.println("real");
    }
  }

  @Tested DoSomething doSomething;
  //@Injectable Call call;

  // nothing happens
  @Test
  public void testRealCall() {
    Deencapsulation.setField(doSomething, "callee", new Call());
    doSomething.execute();
  }

  // invocation doesn't help either
  @Test
  public void testRealCallSecondTry() {
    new MockUp<Call>() {
      @Mock
      @SuppressWarnings("unused")
      public void call(Invocation inv) {
        inv.proceed();
      }
    };
    Deencapsulation.setField(doSomething, "callee", new Call());
    doSomething.execute();
  }

  // this works, but requires redundant methods
  @Test
  public void testRealCallThirdTry() {
    new MockUp<Call>() {
      @Mock
      @SuppressWarnings("unused")
      public void call() {
        System.out.println("real");
      }
    };
    Deencapsulation.setField(doSomething, "callee", new Call());
    doSomething.execute();
  }

  @Test
  public void testFakeCall() {
    new MockUp<Call>() {
      @Mock
      @SuppressWarnings("unused")
      public void call() {
        System.out.println("fake");
      }
    };
    Deencapsulation.setField(doSomething, "callee", new Call());
    doSomething.execute();
  }
}

OTHER TIPS

I ran into this question when I had the same problem. However, the existing answer don't work with newer versions of JMockit.

If a field in the tested class is annotated with @Inject, a corresponding @Injectable is required in the test class. Usually. This means that removing the @Injectable and instead mock the class with MockUp suggested in the other answer doesn't work. JMockit will complain with "Missing @Injectable for field ...".

What needs to be done instead is to change the @Injectable annotation to a @Tested annotation, i.e. change this

@Injectable Call call;

to

@Tested Call call;

call becomes a real instance and JMockit doesn't complain about a missing @Injectable. If you need to mock some methods in call, it can be done with MockUp as usual.

new MockUp<Call>() {
  @Mock
  public void someMethodToMock() {
  }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top