18

Am using JMockit 1.1 and all I want to do is invoke a private method and test the return value. However, I am having trouble understanding exactly how to do this from the JMockit De-Encapsulation example.

The method I am trying to test is the private method in this class:

public class StringToTransaction {
   private List<String> parseTransactionString(final String input) {
      // .. processing
      return resultList;
   }
}

And my test code is below.

@Test
public void testParsingForCommas() {
   final StringToTransaction tested = new StringToTransaction();
   final List<String> expected = new ArrayList<String>();
   // Add expected strings list here..
   new Expectations() {
      {
         invoke(tested, "parseTransactionString", "blah blah");
         returns(expected);
      }
   };
}

And the error I am getting is:

java.lang.IllegalStateException: Missing invocation to mocked type at this point; please make sure such invocations appear only after the declaration of a suitable mock field or parameter

Perhaps I have misunderstood the whole API here, because I don't think I want to mock the class.. just test the result of calling the private method.

Robert Mark Bram
  • 8,104
  • 8
  • 52
  • 73

6 Answers6

39

I think you are making this too complicated. You should not be using the Expectations block at all. All you need to do is something like this:

@Test
public void testParsingForCommas() {
   StringToTransaction tested = new StringToTransaction();
   List<String> expected = new ArrayList<String>();
   // Add expected strings list here..

   List<String> actual = Deencapsulation.invoke(tested, "parseTransactionString", "blah blah");
   assertEquals(expected, actual);
}

Basically, call a private method via Deencapsulation and test that the actual is equal to the expected. Just like you would if the method were public. No mocking is being done, so no Expectations block is needed.

Jeff Olson
  • 6,323
  • 2
  • 22
  • 26
  • Hi @JeffOlson - on which class is that invoke() method? On Expectations, invoke() is a protected instance method. Or maybe just use java.lang.reflect.Method.invoke() and ignore JMockit altogether.. – Robert Mark Bram Mar 25 '13 at 09:17
  • At last I understand... it's `Deencapsulation.invoke`, thanks @JeffOlson and thanks to Rogerio in https://groups.google.com/d/msg/jmockit-users/oEgjW0DfmgU/3pEE1mjt1ncJ. – Robert Mark Bram Mar 25 '13 at 13:22
  • Yes, that's correct. Sorry for not being more clear, but you had used `invoke()` directly in your initial example (presumably via a static import), so I thought I'd leave it as-is. I'll update my answer to be more clear for anyone else who might read this in the future. – Jeff Olson Mar 25 '13 at 14:49
  • I wrote this up here: http://robertmarkbramprogrammer.blogspot.com.au/2013/03/testing-private-method-through.html – Robert Mark Bram Mar 26 '13 at 12:41
  • @JeffOlson In latest jmockit, they have removed this. So is there any alternative for achieving the same behavior? This answer needs to be updated. – IfOnly Jan 30 '19 at 08:43
  • Yes, it seems you are right. I suggest looking at some of the answers on https://stackoverflow.com/questions/880365/any-way-to-invoke-a-private-method. Specifically Spring's ReflectionUtils looks interesting. – Jeff Olson Feb 01 '19 at 02:12
3

At this point, I don't know if JMockit can or should be used for this. Testing my private method can be done with plain old reflection, although I started this exercise to learn about JMockit (and test my code). In case JMockit cannot be used for this, here is how I can use reflection instead.

@Test
public void testParsingForCommas() throws Exception {
   StringToTransaction tested = new StringToTransaction();
   ArrayList<String> expected = new ArrayList<>();
   expected.add("Test");

   Method declaredMethod =
         tested.getClass().getDeclaredMethod("parseTransactionString",
               String.class);
   declaredMethod.setAccessible(true);
   Object actual = declaredMethod.invoke(tested, "blah blah");
   assertEquals(expected, actual);
}

The call to setAccessible(true) is important here or the invoke will blow up when calling a private method.

declaredMethod.setAccessible(true);

But you want to know what is really cool? If you don't call setAccessible(true), it will blow up with a java.lang.StackOverflowError! :)

Robert Mark Bram
  • 8,104
  • 8
  • 52
  • 73
2

As mocking private methods is not allowed in latest Jmockit. One can mock the APIs used inside that private method as a Workaround instead of mocking the private method.

This workaround can also be treated as a final solution.

Example:
Actual Class:

class A {

  private int getId(String name){  //private method
      return DAOManager.getDao().getId(name);  //Call to non-private method can be mocked.
  }
}  

Test Class:

public class ATest{

  @Before
  public void setUp(){
    new MockDAOManager();
  }

  //Mock APIs used by the private method `getId`.
  public static class MockDAOManager extends MockUp<MockDAOManager>{
     static mocked_user_id = 101;

     @Mock
     public DAOManager getDao() throws Exception{
          return new DAOManager();
     }

     @Mock
     public Integer getId(String name){
         return mocked_user_id;
     }
  }
}

Note:

  • If you don't have such logic(private method calls to another non-private method) then you may have to refactor your code, Otherwise this will not work.
  • Here DAOManager.getDao().getId(name) is not a private API.
  • There may be a need to mock all APIs used by that private method.
Rohit Gaikwad
  • 3,677
  • 3
  • 17
  • 40
0

start from 1.35(?) jmockit removed that helper method. for reasons that it is no longer useful (which I don't quite understand)

but yes, this utility is available somewhere else

org.springframework.test.util.ReflectionTestUtils
zinking
  • 5,561
  • 5
  • 49
  • 81
0

As mentioned by @Jeff Olson, you can also call the private methods of a bean by declaring them @Tested.

Here is an example:

// Java

    @Tested
    private YourServiceImplClass serviceImpl;

    @Test
    public void testPrivateMethod() {
   
          List<String> expected = new ArrayList<String>();
          // Add expected strings list here..

          List<String> actual = Deencapsulation.invoke(serviceImpl, "yourPrivateMethod", "arguments");
          assertEquals(expected, actual);
    }
nikhil84
  • 3,235
  • 4
  • 22
  • 43
-2

Why do you want to test the private method directly ? Most of the times API methods i.e. public interface methods are unit tested as private methods will be be indirectly tested as well along with them. You can put assert statements with expected values from private methods where you call them within the public methods. So if assert fails you are sure that there is some issue with the private method. So you need not test it separately.

Ankur Shanbhag
  • 7,746
  • 2
  • 28
  • 38
  • 1
    Hi @Ankur, I apologise, but I am looking for an answer involving how to use JMockit correctly in this situation (or even whether I can). I am not looking to discuss why I should or shouldn't test a private method. I understand that using assertions via public methods is another way to this, but that is not what I am trying to work out here. – Robert Mark Bram Mar 24 '13 at 06:40
  • Take a look at this : http://stackoverflow.com/questions/5729973/jmockit-two-mocked-instances-of-the-same-type – Ankur Shanbhag Mar 24 '13 at 06:44
  • I may be a bit dense @Ankur but I cannot work out how to apply that solution to my issue. :) Putting the **Expectations** block before the initialisation of **tested** results in a compilation error: **tested cannot be resolved to a variable**. – Robert Mark Bram Mar 24 '13 at 06:55
  • I was looking for some solution on the net. I found one good link. Please go through it. http://rockycode.com/blog/easymock-cause-effect-exception-mapping/ – Ankur Shanbhag Mar 24 '13 at 07:04
  • That is using EasyMock, not JMockit @Ankur. Their usage patterns appear to be quite different. – Robert Mark Bram Mar 24 '13 at 08:13
  • 1
    I sometimes do find it makes the most sense to test a private method instead of relying on test coverage via public methods. – Jeff Olson Mar 25 '13 at 05:24