Unit test framework -- Mockito

Problems encountered in unit testing in current development

After the business code development is completed, the new code needs to be unit tested. Due to the large number of third-party components and external system interfaces that the project depends on, the whole project needs to be started and various dependencies need to be loaded every time the unit test is executed. Moreover, due to network constraints, it is sometimes necessary to apply for various ACL S, and the project startup takes a long time, Sometimes just to run a unit test with only a few lines of code, it takes several minutes to wait for the project to start, which is seriously contrary to the original intention of unit testing. This situation leads to the low enthusiasm of everyone to write unit tests, and even directly skip unit tests and directly release the code to the test environment for integration testing.

Solution – Mock

Is there a solution to the above problem? The answer is definitely yes. That is, mock external dependencies and only execute your own code. At present, there are many tools for mock, such as PowerMock,EasyMock,Mockito, etc. The following is a brief introduction to the use of Mockito

Junit4 + Mockito:

Let's start with an example:

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserInfoMapper userInfoMapper;
    @Resource
    private WhiteListCheckService whiteListCheckService;
    @Resource
    private UserLevelRemoteProxy userLevelRemoteProxy;
    
    @Override
    public UserInfoBo queryUserInfo(String userId){
        UserInfoEntity userInfo = userInfoMapper.findById(userId);
        boolean isWhiteListUser = whiteListCheckService.isWhiteListUser(userId);
        String userLevel = userLevelRemoteProxy.queryUserLevelById(userId);
        UserInfoBo userInfoBo = BeanUtils.copy(UserInfoBo.class, userInfo);
        userInfoBo.setUserLevel(userLevel);
        userInfoBo.isWhiteListUser(isWhiteListUser);
        
        return userInfoBo;
    }
}

The above is a common type of service method, but some business logic is omitted. In the method, mapper method will be called to query the database, other service methods inside the system, and external interfaces Now let's use Junit4+Mockito to unit test the method:

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest{
    @InjectMocks
    private UserServiceImpl userService;
    @Mock
    private UserInfoMapper userInfoMapper;
    @Mock
    private UserLevelRemoteProxy userLevelRemoteProxy;
    
    @Test
    public void queryUserInfoTest(){
        UserInfoEntity userInfo = Mockito.mock(UserInfoEntity.class);
        Mockito.when(whiteListCheckService.isWhiteListUser(Mockito.anyString))
            .thenReturn(true);
        Mockito.when(userInfoMapper.findById(Mockito.anyString())).thenReturn(userInfo);
        Mockito.when(userLevelRemoteProxy.queryUserLevelById(Mockito.anyString()))
            .thenReturn("12Lev");
        UserInfoBo userInfoBo = userService.queryUserInfo("123");
        
        Assert.assertEquals("12Lev", userInfoBo.getUserLevel());
        
    }
}

Executing the above method will execute userinfomapper When the findbyid method is used, the instance of UserInfoEntity of mock will be returned. When the whitelistcheckservice The iswhitelistuser method returns true and executes userlevelremoteproxy The queryuserlevelbyid method returns 12Lev. After mocking the dependent interfaces, we can execute the unit test without starting the project, which improves the execution speed of the unit test. At the same time, you can also write multiple unit tests for different branches of the code to further improve the unit test coverage of the code.

Common comments of Mockito:
  • @Mock: create a mock instance
  • @InjectMocks: automatically inject the @ Mock and @ Spy annotated objects. It is generally used to create object instances that need to be tested
  • @Spy: allows you to create partially simulated objects
Common methods of Mockito:
  • Parameter matcher: mockito anyString()/Mockito. anyInt()/Mockito. any()
  • Method execution verifier: mockito verify()
  • Anchor method call and specified return value: mockito when(mock.someMethod()). thenThrow(new RuntimeException). thenReturn("foo")
  • Method of executing real instance: mockito spy()

For more methods, please refer to the official documents: https://javadoc.io/doc/org.mockito/mockito-core/latest/index-files/index-1.html

Tips:
  • Support for Java 8 Lambda matcher (Since 2.1.0):

    @Test
    public void testMethod(){
        List<String> list = Mockito.mock(List.class);
        list.add("111");
        list.add("222");
        list.add("333");
        Mockito.verify(list,Mockito.times(3)).add(Mockito.argThat(s -> s.length() <5));//A maximum of 4 elements are added to the list
    }
    
  • Mocking final type, enums and final methods (Since 2.1.0)

    Mockito supports mock ing final type, enums and final methods from version 2.1.0, but additional configuration is required. For details, please refer to: Mocking final types, enums and final methods

  • Mock the static method (since 3.4.0)

    The mockito core dependency needs to be replaced with mockito inline, and there are requirements for the JDK version. If the jdk8 or earlier version is used, the ByteBuddy dependency needs to be used

    		<dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-inline</artifactId>
                <version>3.7.7</version>
                <scope>test</scope>
            </dependency>
            <!-- If yes jdk8 Or earlier, you need to add the following dependencies -->
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy</artifactId>
                <version>1.12.1</version>
            </dependency>
            <!-- If yes jdk8 Or earlier, you need to add the following dependencies -->
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy-agent</artifactId>
                <version>1.12.1</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
            </dependency>
    
    	@Test
        public void mockStaticTest(){
            LocalDate yearOf2000 = LocalDate.of(2000, 1, 1);
            try (MockedStatic theMock = Mockito.mockStatic(LocalDate.class)) {
                theMock.when(LocalDate::now).thenReturn(yearOf2000);
                Assert.assertEquals(2000, LocalDate.now().getYear());
            }
        }
    
  • mock for private methods

    Mockito does not support mocking private methods. If there are requirements in this regard, you can use ProwerMock in combination. As for why mockito does not support mocking private methods, the official statement is as follows:

    Mockito And Private Methods

summary

As for writing unit tests, many people think that it is not cost-effective. Instead of spending a lot of time writing unit tests for some methods that can see the execution results at a glance, it is better to directly conduct integration tests and run the main process. If there is no problem, you can test them. However, doing so often ignores the test coverage of branch processes, resulting in low code quality, It will even become a hidden danger leading to online problems. Therefore, I think it is necessary to write unit tests. At least, we should cover the newly added code. In addition, there are some test code generation plug-ins that can help us directly generate unit tests. If you are interested, you can try to use them

TestMe

JCode5

Diffblue

reference resources

Mockito official website

Articles on unit testing in 360 digital middleware team blog

Mockito official demo

Keywords: Java unit testing

Added by bijukon on Thu, 24 Feb 2022 05:00:48 +0200