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.