10k

JMockit Basic

1 What is JMockit

Jmockit is a Java framework for mocking objects in tests.

2 An Exapmle

//A class you could say hello in native language 
public class HelloJMockit {
    public String sayHello() {
        Locale locale = Locale.getDefault();
        if (locale.equals(Locale.CHINA)) {
            // Say Chinese in China
            return "你好,JMockit!";
        } else {
            // Say English in other countries
            return "Hello,JMockit!";
        }
    }
}


public class HelloJMockitTest { 

    @Test
    public void testSayHelloAtChina() {
        // Suppose we are in China
        new Expectations(Locale.class) {
            {
                Locale.getDefault();
                result = Locale.CHINA;
            }
        };
 
        Assert.assertTrue("你好,JMockit!".equals((new HelloJMockit()).sayHello()));
    }
 
 
    @Test
    public void testSayHelloAtUS() {
        // Suppose we are in the US
        new Expectations(Locale.class) {
            {
                Locale.getDefault();
                result = Locale.US;
            }
        };
 
        Assert.assertTrue("Hello,JMockit!".equals((new HelloJMockit()).sayHello()));
    }
}

Here is a example domostrate the workflow of JMockit. You will learn how to use those annotations, what is Expectation and so on.

3 Basic

1. Configuration Dependencies

2. Code Structure

A normal test case contains test parameter and test method which consisted with record, replay and verification.

1. Attribute

We can use annotations(@Tested, Mocked, Injectable, Capturing)on these parameters. We will have a detailed explanation on when to use them.

2. Parameter

The parameter of test method.

Junit doesn't allow parameter in test method except thoese with Jmockit annotations.

The difference between parameters and attributes is the scope. Parameter only applys to the method whereas attribute can be used accross the test class.

3. Record-Replay-Verification

Record: Customized return when receiving some particular inputs. Replay: replay the test logic/process. Verification: verification after replay. E.g. times of a method has been called.

public class ProgramConstructureTest {
 
    // This is a test parameter
    @Mocked
    HelloJMockit helloJMockit;
 
    @Test
    public void test1() {
        // Record
        new Expectations() {
            {
                helloJMockit.sayHello();
                // You are expecting "hello,david" rather than "hello,JMockit"
                result = "hello, david";
            }
        };
        // Replay
        String msg = helloJMockit.sayHello();
        Assert.assertTrue(msg.equals("hello, david"));
        // Verification
        new Verifications() {
            {
                helloJMockit.sayHello();
 
                times = 1;
            }
        };
    }
 
    @Test
    public void test2(@Mocked HelloJMockit helloJMockit /* This is a test parameter */) {
        // Record
        new Expectations() {
            {
                helloJMockit.sayHello();
                // You are expecting "hello,david" rather than "hello,JMockit"
                result = "hello,david";
            }
        };
        // Replay
        String msg = helloJMockit.sayHello();
        Assert.assertTrue(msg.equals("hello,david"));
        // Verification
        new Verifications() {
            {
                helloJMockit.sayHello();
                // verify helloJMockit.sayHello() had been called once
                times = 1;
            }
        };
    }
}

3. @Mocked

  1. When using the @Mocked annotation on a field, it will create mocked instances(with default value) of each and every new object of that particular class.

  2. When our test has some dependent interfaces, we could use @Mocked on them and they will create an instance for them. For example, we are in a distributed system and there is a dependency on remote interface, we could use @Mocked to create an instance.

  3. Mocking a class

    ```java public class MockedClassTest { // JMockit will instatiate this locale @Mocked Locale locale;

    // @Mocked on a class
    @Test
    public void testMockedClass() {
        // Static methods will return null
        Assert.assertTrue(Locale.getDefault() == null);
        // Non static methods will return null
        Assert.assertTrue(locale.getCountry() == null);
        // So as well a new one defined by yourself
        Locale chinaLocale = new Locale("zh", "CN");
        Assert.assertTrue(chinaLocale.getCountry() == null);
    }
    

    } ```

  4. Mocking a interface/abstruct class

    ```java public class MockedInterfaceTest {

    @Mocked
    HttpSession session;
    
    // when @Mocked on interface
    @Test
    public void testMockedInterface() {
        Assert.assertTrue(session.getId() == null);
        Assert.assertTrue(session.getCreationTime() == 0L);
        Assert.assertTrue(session.getServletContext() != null);
        Assert.assertTrue(session.getServletContext().getContextPath() == null);
    }
    

    } ```

4. @Tested & @Injectable

  1. With the @Injectable annotation, only one mocked instance will be created.

  2. @Tested means this is the class to be tested.

  3. When you need to manage dependencies of class to be tested, you could use these to annotations.

  4. different between injetable and mocked

    ```java public class MockedAndInjectable {

    @Test
    public void testMocked(@Mocked Locale locale) {
        // Static methods will return null(mocked)
        Assert.assertTrue(Locale.getDefault() == null);
        // Non static methods will return null(mocked)
        Assert.assertTrue(locale.getCountry() == null);
        // So as well a new one defined by yourself(mocked)
        Locale chinaLocale = new Locale("zh", "CN");
        Assert.assertTrue(chinaLocale.getCountry() == null);
    }
    
    @Test
    public void testInjectable(@Injectable Locale locale) {
        // Static method will not be mocked
        Assert.assertTrue(Locale.getDefault() != null);
        // mocked but only apply to this 'locale'
        Assert.assertTrue(locale.getCountry() == null);
        // If you create new instance, it won't be affected
        Locale chinaLocale = new Locale("zh", "CN");
        Assert.assertTrue(chinaLocale.getCountry().equals("CN"));
    }
    

    }

    ```

  5. Use of Injectable and Tested

    java public interface MailService { public boolean sendMail(long userId, String content); }

    ```java

    public interface UserCheckService { public boolean check(long userId); } ```

    ```java public class OrderService {

    MailService mailService;
    @Resource
    UserCheckService userCheckService;
    
    public OrderService(MailService mailService) {
        this.mailService = mailService;
    }
    
    public boolean submitOrder(long buyerId, long itemId) {
    
        if (!userCheckService.check(buyerId)) {
            return false;
        }
        // order logic
        ...
    
        if (!this.mailService.sendMail(buyerId, "下单成功")) {
            return false;
        }
        return true;
    }
    

    } ```

    If we want to test submitOrder but it depends on the MailService and UserCheckService and we don't want to connect to the mail server nad user authentication server.

    ```java public class TestedAndInjectable { //JMockit will initiate this parameter @Tested OrderService orderService; long testUserId = 123456l; long testItemId = 456789l;

    @Test public void testSubmitOrder(@Injectable MailService mailService, @Injectable UserCheckService userCheckService) { new Expectations() { { mailService.sendMail(testUserId, anyString); result = true; userCheckService.check(testUserId); result = true; } }; // JMockit instatiated mailService and injected to OrderService via attribute in orderSerice //JMockit instatiated userCheckService and injected to OrderService via attribute in orderSerice Assert.assertTrue(orderService.submitOrder(testUserId, testItemId)); } }

    ```

5. @Capturing

The last annotation, @Capturing will behave like @Mocked, but will extend its reach to every subclass extending or implementing the annotated field's type.

6. Expectation

new Expectations() {
    // this is an Expectations anonymous inner class
    {
        //method call
        //result = ...
        //other method call
        //result = ...
        // ...
    }
};
 
// we can define another record before replay. 
new Expectations() {
      
    {
         //...
    }
};
  1. Use Mocked objects to record

    ```java public class ExpectationsTest { @Mocked Calendar cal;

    @Test
    public void testRecordOutside() {
        new Expectations() {
            {
                cal.get(Calendar.YEAR);
                result = 2016;
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;
            }
        };
        Assert.assertTrue(cal.get(Calendar.YEAR) == 2016);
        Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
        Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 0);
    }
    

    } ```

  2. Build injection methods

    ```java public class ExpectationsConstructorTest2 {

    @Test
    public void testRecordConstrutctor1() {
        Calendar cal = Calendar.getInstance();
        // Pass the class to be mock to as parameter, you could only mock part of the actions
        new Expectations(Calendar.class) {
            {
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;
            }
        };
        Calendar now = Calendar.getInstance();
        Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
        Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
    }
    
    @Test
    public void testRecordConstrutctor2() {
        Calendar cal = Calendar.getInstance();
        // Pass the class to be mock to as parameter, you could only mock part of the actions and only apply to this object
        new Expectations(cal) {
            {
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;
            }
        };
    
        Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
    
        Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
    
        // now is another object and will not be mocked
        Calendar now = Calendar.getInstance();
        Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == (new Date()).getHours());
        Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
    }
    

    } ```

7. MockUp & @Mock

public class MockUpTest {
 
    @Test
    public void testMockUp() {
        // customize the get() in Calender.class
        new MockUp<Calendar>(Calendar.class) {
            @Mock
            public int get(int unit) {
                if (unit == Calendar.YEAR) {
                    return 2017;
                }
                if (unit == Calendar.MONDAY) {
                    return 12;
                }
                if (unit == Calendar.DAY_OF_MONTH) {
                    return 25;
                }
                if (unit == Calendar.HOUR_OF_DAY) {
                    return 7;
                }
                return 0;
            }
        };
        Calendar cal = Calendar.getInstance(Locale.FRANCE);
        Assert.assertTrue(cal.get(Calendar.YEAR) == 2017);
        Assert.assertTrue(cal.get(Calendar.MONDAY) == 12);
        Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 25);
        Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
        // Other methods won't be mocked.
        Assert.assertTrue((cal.getFirstDayOfWeek() == Calendar.MONDAY));
 
    }
}
  1. Limitation

    1. A class has many instatnces and you need to mock only one of them;
    2. Mock on class that dynamically generated by AOP;
    3. More codes.
  2. MockUp & @Mock is suitable for mock some common methods and reduce new Exceptations.

8. Verifications

  1. Verificaltions are used to verify the times or order for paticular methods called.
new Verifications() {
    {
        //method call
        //times/minTimes/maxTimes 
        // ... 
        //another method call
        //times/minTimes/maxTimes 
    }
};
  
// you can specify another verification after replay
new Verifications() {
       
    {
         //...
    }
};
public class VerificationTest {
    @Test
    public void testVerification() {
        //record
        Calendar cal = Calendar.getInstance();
        new Expectations(Calendar.class) {
            {
                cal.get(Calendar.YEAR);
                result = 2016;
                cal.get(Calendar.HOUR_OF_DAY);
                result = 7;
            }
        };
        // replay
        Calendar now = Calendar.getInstance();
        Assert.assertTrue(now.get(Calendar.YEAR) == 2016);
        Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
        // verify
        new Verifications() {
            {
                Calendar.getInstance();
                times = 1;
                cal.get(anyInt);
                times = 2;
            }
        };
 
    }
}
  1. In daily we tends to use Assert to verify some results rather than caring about if a method has been called and so on. So in verification stage, we could use Assert to replace new Verifications.

JMockit中文网

JMockit 101

Thoughts? Leave a comment