Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4535

JMockit Advanced Usage

$
0
0

1. Introduction

In this article, we’ll go beyond the JMockit basics and we’ll start looking at some advanced scenarios, such as:

  • Faking (or the MockUp API)
  • The Deencapsulation utility class
  • How to mock more than one interface using only one mock
  • How to reuse expectations and verifications

If you want to discover JMockit’s basics, check other articles from this series. You can find relevant links at the bottom of the page.

2. Private Methods/Inner Classes Mocking

Mocking and testing of private methods or inner classes is often not considered good practice.

The reasoning behind it is that if they’re private, they shouldn’t be tested directly as they’re the innermost guts of the class, but sometimes it still needs to be done, especially when dealing with legacy code.

With JMockit, you have two options to handle these:

  • The MockUp API to alter the real implementation (for the second case)
  • The Deencapsulation utility class, to call any method directly (for the first case)

All following examples will be done for the following class and we’ll suppose that are run on a test class with the same configuration as the first one (to avoid repeating code):

public class AdvancedCollaborator {
    int i;
    private int privateField = 5;

    // default constructor omitted 
    
    public AdvancedCollaborator(String string) throws Exception{
        i = string.length();
    }

    public String methodThatCallsPrivateMethod(int i) {
        return privateMethod() + i;
    }
    public int methodThatReturnsThePrivateField() {
        return privateField;
    }
    private String privateMethod() {
        return "default:";
    }

    class InnerAdvancedCollaborator {...}
}

2.1. Faking with MockUp

JMockit’s Mockup API provides support for the creation of fake implementations or mock-ups. Typically, a mock-up targets a few methods and/or constructors in the class to be faked, while leaving most other methods and constructors unmodified. This allows for a complete re-write of a class, so any method or constructor (with any access modifier) can be targeted.

Let’s see how we can re-define privateMethod() using the Mockup’s API:

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest {

    @Tested
    private AdvancedCollaborator mock;

    @Test
    public void testToMockUpPrivateMethod() {
        new MockUp<AdvancedCollaborator>() {
            @Mock
            private String privateMethod() {
                return "mocked: ";
            }
        };
        String res = mock.methodThatCallsPrivateMethod(1);
        assertEquals("mocked: 1", res);
    }
}

In this example we’re defining a new MockUp for the AdvancedCollaborator class using the @Mock annotation on a method with matching signature. After this, calls to that method will be delegated to our mocked one.

We can also use this to mock-up the constructor of a class that needs specific arguments or configuration in order to simplify tests:

@Test
public void testToMockUpDifficultConstructor() throws Exception{
    new MockUp<AdvancedCollaborator>() {
        @Mock
        public void $init(Invocation invocation, String string) {
            ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1;
        }
    };
    AdvancedCollaborator coll = new AdvancedCollaborator(null);
    assertEquals(1, coll.i);
}

In this example, we can see that for constructor mocking you need to mock the $init method. You can pass an extra argument of type Invocation, with which you can access information about the invocation of the mocked method, including the instance to which the invocation is being performed.

2.2. Using the Deencapsulation Class

JMockit includes a test utility class: the Deencapsulation. As its name indicates, it’s used to de-encapsulate a state of an object, and using it, you can simplify testing by accessing fields and methods that could not be accessed otherwise.

You can invoke a method:

@Test
public void testToCallPrivateMethodsDirectly(){
    Object value = Deencapsulation.invoke(mock, "privateMethod");
    assertEquals("default:", value);
}

You can also set fields:

@Test
public void testToSetPrivateFieldDirectly(){
    Deencapsulation.setField(mock, "privateField", 10);
    assertEquals(10, mock.methodThatReturnsThePrivateField());
}

And get fields:

@Test
public void testToGetPrivateFieldDirectly(){
    int value = Deencapsulation.getField(mock, "privateField");
    assertEquals(5, value);
}

And create new instances of classes:

@Test
public void testToCreateNewInstanceDirectly(){
    AdvancedCollaborator coll = Deencapsulation
      .newInstance(AdvancedCollaborator.class, "foo");
    assertEquals(3, coll.i);
}

Even new instances of inner classes:

@Test
public void testToCreateNewInnerClassInstanceDirectly(){
    InnerCollaborator inner = Deencapsulation
      .newInnerInstance(InnerCollaborator.class, mock);
    assertNotNull(inner);
}

As you can see, the Deencapsulation class is extremely useful when testing air tight classes. One example could be to set dependencies of a class that uses @Autowired annotations on private fields and has no setters for them, or to unit test inner classes without having to depend on the public interface of its container class.

3. Mocking Multiple Interfaces in One Same Mock

Let’s assume that you want to test a class – not yet implemented – but you know for sure that it will implement several interfaces.

Usually, you wouldn’t be able to test said class before implementing it, but with JMockit you have the ability to prepare tests beforehand by mocking more than one interface using one mock object.

This can be achieved by using generics and defining a type that extends several interfaces. This generic type can be either defined for a whole test class or for just one test method.

For example, we’re going to create a mock for interfaces List and Comparable two ways:

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest<MultiMock
  extends List<String> & Comparable<List<String>>> {
    
    @Mocked
    private MultiMock multiMock;
    
    @Test
    public void testOnClass() {
        new Expectations() {{
            multiMock.get(5); result = "foo";
            multiMock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", multiMock.get(5));
        assertEquals(0, multiMock.compareTo(new ArrayList<>()));
    }

    @Test
    public <M extends List<String> & Comparable<List<String>>>
      void testOnMethod(@Mocked M mock) {
        new Expectations() {{
            mock.get(5); result = "foo";
            mock.compareTo((List<String>) any); result = 0; 
        }};
        assertEquals("foo", mock.get(5));
        assertEquals(0, mock.compareTo(new ArrayList<>()));
    }
}

As you can see in the line 2, we can define a new test type for the whole test by using generics on the class name. That way, MultiMock will be available as a type and you’ll be able to create mocks for it using any of JMockit’s annotations.

In lines from 7 to 18, we can see an example using a mock of a multi-class defined for the whole test class.

If you need the multi-interface mock for just one test, you can achieve this by defining the generic type on the method signature and passing a new mock of that new generic as the test method argument. In lines 20 to 32, we can see an example of doing so for the same tested behavior as in the previous test.

4. Reusing Expectations and Verifications

In the end, when testing classes, you may encounter cases where you’re repeating the same Expectations and/or Verifications over and over. To ease that, you can reuse both easily.

We’re going to explain it by an example (we’re using the classes Model, Collaborator, and Performer from our JMockit 101 article):

@RunWith(JMockit.class)
public class ReusingTest {

    @Injectable
    private Collaborator collaborator;
    
    @Mocked
    private Model model;

    @Tested
    private Performer performer;
    
    @Before
    public void setup(){
        new Expectations(){{
           model.getInfo(); result = "foo"; minTimes = 0;
           collaborator.collaborate("foo"); result = true; minTimes = 0; 
        }};
    }

    @Test
    public void testWithSetup() {
        performer.perform(model);
        verifyTrueCalls(1);
    }
    
    protected void verifyTrueCalls(int calls){
        new Verifications(){{
           collaborator.receive(true); times = calls; 
        }};
    }
    
    final class TrueCallsVerification extends Verifications{
        public TrueCallsVerification(int calls){
            collaborator.receive(true); times = calls; 
        }
    }
    
    @Test
    public void testWithFinalClass() {
        performer.perform(model);
        new TrueCallsVerification(1);
    }
}

In this example, you can see in lines from 15 to 18 that we’re preparing an expectation for every test so that model.getInfo() always returns “foo” and for collaborator.collaborate() to always expect “foo” as the argument and returning true. We put the minTimes = 0 statement so no fails appear when not actually using them in tests.

Also, we’ve created method verifyTrueCalls(int) to simplify verifications to the collaborator.receive(boolean) method when the passed argument is true.

Lastly, you can also create new types of specific expectations and verifications just extending any of Expectations or Verifications classes. Then you define a constructor if you need to configure the behavior and create a new instance of said type in a test as we do in lines from 33 to 43.

5. Conclusion

With this installment of the JMockit series, we have touched on several advanced topics that will definitely help you with everyday mocking and testing.

We may do more articles on JMockit, so stay tuned to learn even more.

And, as always, the full implementation of this tutorial can be found on the GitHub.

5.1. Articles in the Series

All articles of the series:


Viewing all articles
Browse latest Browse all 4535

Trending Articles