1. What is a mock test
1.png
Mock testing is to use a virtual object (Mock object) to create a test method for testing in the test activity, for some more complex data/scenario that is not easy to construct or obtain .
2. Why Mock Test
Mock is to solve the problem that it is difficult to develop and test due to coupling between different units. Therefore, Mock can appear not only in unit tests , but also in integration tests and system tests .
Mock's biggest function is to help you decompose the coupling of unit tests. If your code has dependencies on another class or interface, it can help you simulate these dependencies and help you verify the behavior of the called dependencies.
3. Mock application scenarios
1. It is necessary to separate the current unit under test from its dependent modules, construct an independent test environment, and not pay attention to the dependent objects of the unit under test, but only focus on the functional logic of the unit under test.
2. The module that the unit under test depends on has not been developed A, and the unit under test needs to rely on the return value of the module for subsequent processing.
3. In the front-end and back-end projects, before the development of the back-end interface is completed, the joint debugging of the interface
4. The interface of the dependent upstream project has not been developed yet, and the joint debugging test of the interface is required
5. Objects that the unit under test depends on are difficult to simulate or have complex structures
For example: There are many abnormal conditions in the payment business, but simulating such abnormal conditions is very complicated or impossible to simulate.
4. Code example
New test project
package com.echo.mockito;
public class demo {
//新建一个测试方法
public int add(int a, int b){
return a + b;
}
}
Build a mock test method
Select the test class, right click and select generate
2.png
click test
3.png
After clicking ok, the corresponding test method will be generated in the test directory, corresponding to the real directory
4.png
5. Parameter method description
@BeforeEach
It is used to prepare before the test. Many environment configurations or basic configurations will be built before the test, which can be set here.
@AfterEach
For post-test setup.
@Mock
Annotations can be understood as a substitute for the mock method, instead of following the real method and simulating the behavior of the real method. When using this annotation, use the MockitoAnnotations.openMocks(this) method to make the annotation take effect.
@Spy
1. The spy object will follow the real method, but the mock object will not,
2. The parameter of the spy method is the object instance, and the parameter of the mock is the class.
@InjectMocks
Used to inject mock variables marked with @Mock into test classes.
MockitoAnnotations.openMocks(this)
Turn on the mock and test it with the above two annotations. It is generally placed in @BeforeEach and turned on before the test, so that it does not need to be turned on in every method.
Mockito.when(demo.add(1,2)).thenReturn(3): piling
The mock core can set the result of the method to be tested, so that the execution result of the real method will be ignored, and subsequent tests will be executed based on the piling result.
Mockito.when(demo.add(1,2)).thenThrow(new RuntimeException());
Used to simulate exceptions.
Assertions.assertEquals(3,demo.add(1,2)): Assertions
The primary means of testing, on which results are judged. (expected value, actual value).
6. Simple test
The setting method of piling test returns 4, but the actual execution is 3, and the test fails.
5.png
No piling test Because it is a spy method, the real method will be used, and the test will pass.
6.png
If it is a mock method, if there is no piling, there will be a default value, and the test will fail. You can try it.
7.png
7. Description of test method
See the code for detailed calls, generally as follows:
-
Piling test
-
exception test
-
real method call
package com.echo.mockito;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
class demoTest {
@Mock
demo demo;
//测试前开启mock
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void add() {
//mock 打桩,就是不管真实的方法如何执行,我们可以自行假设该方法执行的结果
//后续的测试都是基于打桩结果来走
// Mockito.when(demo.add(1,2)).thenReturn(4);
// Assertions.assertEquals(3,demo.add(1,2));
//当测试方法出现异常,测试方法 如果有try{}catch{} 则可以测试异常是否正常
//Mockito.when(demo.add(1,1)).thenThrow(new RuntimeException());
//调用真实的方法
Mockito.when(demo.add(1,1)).thenCallRealMethod();
Assertions.assertEquals(2,demo.add(1,1));
}
@AfterEach
void after(){
System.out.println("测试结束");
}
}
8. Mock static method
In the previous version, it was not allowed to simulate and test static methods. If you need to test static methods, you need to replace the new mock dependencies and comment out the current dependencies, which will cause conflicts.
Static method testing should use mock's MockedStatic class to construct the test method.
<!-- <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.6.1</version>
</dependency>
-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
package com.echo.mockito.Util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
class StaticUtilsTest {
@BeforeEach
void setUp() {
}
// 有参静态方法构建
@Test
void range() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));
Assertions.assertTrue(StaticUtils.range(2,6).contains(11));
}
// 无参静态方法构建
@Test
void name() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(StaticUtils::name).thenReturn("dhmw");
Assertions.assertEquals("dhmw",StaticUtils.name());
}
}
Problem: There is no problem with the execution of a single method, but when we execute all the methods on the class, we find that an error is reported.
提示static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered
This means that each method needs to have its own static mock object, which is not allowed to be shared. When executed together, the first method occupies the object, and the second method has no way to occupy it.
8.png
Solution: Close the mock object demo.close() directly after each method is executed. Equivalent to a singleton. Release it after use, and the next method can then be used.
@Test
void range() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));
Assertions.assertTrue(StaticUtils.range(2,6).contains(11));
//关闭
demo.close();
}
// 无参静态方法构建
@Test
void name() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(StaticUtils::name).thenReturn("dhmw");
Assertions.assertEquals("dhmw",StaticUtils.name());
//关闭
demo.close();
}
9. Improve test coverage
Case: In the data statistics system, the local pusher enters the customer's name and mobile phone number, and finally builds a user object and stores it in the data table.
The business code is as follows:
package com.echo.mockito.service.impl;
import com.echo.mockito.dao.UserDao;
import com.echo.mockito.service.RegistrationService;
import com.echo.mockito.vo.User;
import org.springframework.beans.factory.annotation.Autowired;
import javax.xml.bind.ValidationException;
import java.sql.SQLException;
public class RegistrationServiceImpl implements RegistrationService {
@Autowired
UserDao userDao;
@Override
public User register(String name, String phone) throws Exception {
if (name == null || name.length() == 0){
throw new ValidationException("name 不能为空");
}
if (phone == null || phone.length() ==0 ){
throw new ValidationException("phone 不能为空");
}
User user;
try {
user = userDao.save(name,phone);
}catch (Exception e){
throw new Exception("SqlException thrown" + e.getMessage());
}
return user;
}
}
package com.echo.mockito.dao;
import com.echo.mockito.vo.User;
public class UserDao {
public User save(String name,String phnoe){
User user = new User();
user.setName(name);
return user;
}
}
To generate the corresponding test code, there are several issues to consider at this time.
1. The class to be tested is RegistrationServiceImpl, but how does the userDao in it be injected into the test class?
2. Because of the need for test coverage, it is necessary to consider that there are several situations in the test class that need to be tested.
(1): Two ifs throw two exceptions, and there are a total of 2 situations to be tested.
(2): Save the database is divided into normal save and abnormal save, a total of 2 cases of testing.
In summary, testing for these four cases must be done in order to cover all the code.
In the same way, we generate test cases . There are detailed instructions in the code, and there are test cases for each case.
package com.echo.mockito.service.impl;
import com.echo.mockito.dao.UserDao;
import com.echo.mockito.vo.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;
import javax.xml.bind.ValidationException;
import java.sql.SQLException;
import static org.junit.jupiter.api.Assertions.*;
class RegistrationServiceImplTest {
@InjectMocks //RegistrationServiceImpl 实例中注入@Mock标记的类,此处是注入userDao
@Spy
private RegistrationServiceImpl registrationService;
@Mock
private UserDao userDao;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void register() throws Exception {
// ------------------ 第一种 name 异常情况 测试 start ------------------------
String name = null;
String phone = "1234";
try {
registrationService.register(name,phone);
}catch (Exception e){
Assertions.assertTrue(e instanceof ValidationException);
}
// ------------------ name 异常情况 测试 end ------------------------
// ------------------ 第二种 phone 异常情况 测试 start ------------------------
name = "111";
phone = null;
try {
registrationService.register(name,phone);
}catch (Exception e){
Assertions.assertTrue(e instanceof ValidationException);
}
// ------------------ phone 异常情况 测试 start ------------------------
// ------------------ 第三种 userDao.save 正常情况 测试 start ------------------------
name = "111";
phone = "111";
//正常保存测试 打桩 走真实的方法
Mockito.when(userDao.save(name,phone)).thenCallRealMethod();
User user = registrationService.register(name,phone);
Assertions.assertEquals("111",user.getName());
// ------------------ userDao.save 正常情况 测试 end ------------------------
// ------------------ 第四种 userDao.save 异常情况 测试 start ------------------------
//异常保存测试 打桩 通过thenThrow 抛出异常 测试异常是否被捕获
Mockito.when(userDao.save(name,phone)).thenThrow(new RuntimeException());
try {
registrationService.register(name,phone);
}catch (Exception e){
Assertions.assertTrue(e instanceof Exception);
}
// ------------------ userDao.save 异常情况 测试 end ------------------------
}
}
As shown above: The first question mentioned above, how to inject member variables in the test class, can be done through @InjectMocks annotation.
The test coverage method is as follows:
9.png
The test results are as follows:
1. The right part will display the test coverage.
2. The green color of the real code represents the covered test, and the red represents the non-covered test.
11.png
If all test cases are 100% covered, the result is as follows:
12.png
In summary, the method of coverage testing is summarized as follows:
1. According to the business code, analyze all the situations that need to be tested.
2. Write specific test codes according to different test situations.
3. For each situation, you can write specific test codes, and then exhaust all the inventory situations by piling, assertion, etc.
question:
1. If the real code method level has throws Exception, then similarly, the test method must also throw exceptions at the method level, otherwise the test will report an error.
@Test
void register() throws Exception {
2. The saved database is divided into normal and abnormal. Then test the normal branch first, and then test the abnormal branch. If the order is reversed, the test will throw an exception first, and the normal branch will not be executed, which will lead to incomplete test coverage.
The following is the supporting information. For friends who do [software testing], it should be the most comprehensive and complete preparation warehouse. This warehouse also accompanied me through the most difficult journey. I hope it can help you too!
Software testing interview applet
The software test question bank maxed out by millions of people! ! ! Who is who knows! ! ! The most comprehensive quiz mini program on the whole network, you can use your mobile phone to do the quizzes, on the subway or on the bus, roll it up!
The following interview question sections are covered:
1. Basic theory of software testing, 2. web, app, interface function testing, 3. network, 4. database, 5. linux
6. web, app, interface automation, 7. performance testing, 8. programming basics, 9. hr interview questions, 10. open test questions, 11. security testing, 12. computer basics
Information acquisition method: