Summary of jmockit use

Keywords: Java Junit Maven

The company has high requirements for unit testing of developers, requiring branch coverage and row coverage to be more than 60%. Jmockit, a powerful mock framework, has been integrated in the project. It is imperative to learn to use this framework. From the first time I couldn't write at all, to being able to cope with work requirements, I stepped on many pits and learned a lot. The following is a brief summary of the use of jmockit framework, focusing on the use of MockUp, because this way of simulation is used in the project.

I. framework integration

Add maven dependency

 <dependencies>
        <!-- jmockit Must be written in junit before -->
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.16</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>

 

II. Introduction to @ Mocked simulation mode

@Mocked simulation, completed by recording, playback and verification, is a complete simulation method for all methods of all instances of a class.

/**
 * Tested class
 */
public class App {

    public String say() {
        return "Hello World";
    }

    public String say2(){
        return "Hello World 2";
    }

    public static String staticSay() {
        return "Still hello world";
    }
}
/**
 * Test class
 */
public class AppTest {

    /**
     * For the overall simulation of the class and all instances, the unwritten recorded method returns 0, null, etc. by default
     */
    @Mocked
    App app;

    @Test
    public void testSay() {

        //Record, define the return value of the simulated method, record multiple behaviors, write in one brace, separate multiple braces
        new Expectations() {{
            app.say();
            result = "say";
        }};

        //playback,Call the simulated method
        System.out.println(app.say()); //say
        System.out.println(new App().say()); //say
        System.out.println(App.staticSay()); //null

        //Verification
        new Verifications() {{
            //Verification say The simulation method was called twice
            app.say();
            times = 2;

            //Verification staticSay The impersonation method was called and was called once
            App.staticSay();
            times = 1;
        }};

    }
}

 

III. introduction to @ Injectable simulation mode

@The way of Injectable and @ Mocked is very similar. The difference is that @ Injectable only simulates the current instance.  

/**
 * Test class
 */
public class AppTest001 {

    /**
     * Overall simulation for the current instance only
     */
    @Injectable
    App app;

    @Test
    public void testSay() {

        //Recording
        new Expectations() {{
            app.say();
            result = "say";
        }};

        //playback
        System.out.println(app.say()); //say,Analog value
        System.out.println(app.say2()); //null,Simulation defaults

        final App appNew = new App();
        System.out.println(appNew.say()); //Hello World,Not simulated, method actual
        System.out.println(App.staticSay()); //Still hello world,Not simulated, method actual

        //Verification
        new Verifications() {
            {
                //Verification say Simulation method called
                app.say();
                times = 1;
            }
            {
                appNew.say();
                times = 1;
            }
            {
                //Verification staticSay The impersonation method was called and was called once
                App.staticSay();
                times = 1;
            }
        };

    }
}

 

4. Expectations, local simulation

/**
 * Test class
 */
public class AppTest002 {

    @Test
    public void testSay() {

        final App app = new App();

        //Recording,Local simulation with parameters [local simulation for all instances, specific to the simulation of one method, other methods are not simulated]
        new Expectations(App.class) {{
            app.say();
            result = "say";
        }};

        //playback
        System.out.println(app.say()); //say,Analog value
        System.out.println(app.say2()); //Hello World 2 ,Not simulated, method actual
        System.out.println(new App().say()); //say,Analog value
        System.out.println(App.staticSay()); //Still hello world,Not simulated, method actual

    }
}

 

V. MockUp local simulation, which can rewrite the logic of the original method, is relatively flexible and recommended

/**
 * Test class
 */
public class AppTest003 {

    @Test
    public void testSay() {
        //Local simulation [for local simulation of all instances, specific to the simulation of one method, other methods are not simulated]
       new MockUp<App>(App.class){

           @Mock
           String say(){
               return "say";
           }
       };

        //playback
        System.out.println(new App().say()); //say,Analog value
        System.out.println(new App().say2()); //Hello World 2,Not simulated, method actual
        System.out.println(App.staticSay()); //Still hello world,Not simulated, method actual

    }
}

 

Vi. how does MockUp simulate private methods, static methods, static blocks, constructors, etc

1. Simulate private properties (instance properties and class properties), which are not supported by MockUp. Use the following methods

//Fields of simulation instance
Deencapsulation.setField(Object objectWithField, String fieldName, Object fieldValue)

//Static fields of the impersonation class
Deencapsulation.setField(Class<?> classWithStaticField, String fieldName, Object fieldValue)

2. Simulate the private method, which is not supported by MockUp. Use the following method

//Simulation example method, note that the parameter cannot be null,If you want to pass on null Please use another overloaded method with parameter type
Deencapsulation.invoke(Object objectWithMethod, String methodName, Object... nonNullArgs)

//Simulation class method
Deencapsulation.invoke(Class<?> classWithStaticMethod, String methodName, Object... nonNullArgs)

3. Simulation static method

As with the simulation example method, you can remove static

4. Analog static block

 //mock Static code block
 @Mock
 void $clinit(Invocation invocation){

 }

5. Simulate instance block and constructor

//mock Code blocks and constructors
@Mock
void $init(Invocation invocation) {

}

6. Analog interface, not supported by MockUp, @ Capturing

/**
 * Simulated interface
 */
public interface IUserService {

    String getUserName( );
}
public class UserServiceImpl implements IUserService {

    @Override
    public String getUserName() {
        return "Bob";
    }
}
/**
 * Interface simulation test
 */
public class IUserServiceTest {

    /**
     * MockUp Cannot mock interface method, can be used to generate interface instance
     */
    @Test
    public void getUserNameTest001(){
        MockUp<IUserService> mockUp = new MockUp<IUserService>(){

            @Mock
            String getUserName( ){
                return "Jack";
            }
        };

        IUserService obj = new UserServiceImpl();
        System.out.println(obj.getUserName()); //Bob,mock fail

        obj = mockUp.getMockInstance();
        System.out.println(obj.getUserName()); //Jack,mockUp The generated instance is the same as writing an interface implementation

        obj = new UserServiceImpl();
        System.out.println(obj.getUserName()); //Bob,mock fail
    }

    /**
     * @Capturing Annotations can implement the mock interface, and all instances of implementation classes are mocked
     * @param base
     */
    @Test
    public void getUserNameTest002(@Capturing final IUserService base){
        IUserService obj = new UserServiceImpl();
        System.out.println(obj.getUserName()); //mock Success, return to simulation default null

        //Recording
        new Expectations(){
            {
                base.getUserName();
                result  = "Jack";
            }
        };
        System.out.println(obj.getUserName()); //Jack

        obj = new IUserService() {
            @Override
            public String getUserName() {
                return "Alice";
            }
        };
        System.out.println(obj.getUserName()); //Jack
    }
    
}

 

Seven. Calling the original method in the MockUp simulation method.

/**
 * Simulation method call original method logic test
 */
public class JSONObjectTest {

    @Test
    public void getTest(){
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("a","A");
        jsonObject.put("b","B");
        jsonObject.put("c","C");
        System.out.println(jsonObject.get("a")); //A
        System.out.println(jsonObject.get("b")); //B
        System.out.println(jsonObject.get("c")); //C
        new MockUp<JSONObject>(){
            @Mock
            Object get(Invocation invocation,Object key){
                if("a".equals(key)){
                    return "aa";
                }else{
                    //Call the original logic
                    return  invocation.proceed(key);
                }
            }
        };
        System.out.println(jsonObject.get("a")); //aa
        System.out.println(jsonObject.get("b")); //B
        System.out.println(jsonObject.get("c")); //C
    }
}

 

VIII. Possible reasons for failure of single run and batch run of MockUp unit test case

1. The test method uses shared variables to influence each other.

2. In a test method, multiple mockups of the same class can be used. It can be solved by writing all the methods of a class that need mocks in a new MockUp. The reason is unknown.

Posted by torsoboy on Sat, 09 Nov 2019 01:51:47 -0800