1 Why use mock for unit testing
If you use SpringbootTest for unit testing, then the spring service will be started, the container and bean will be generated and all external resources or dependencies will be loaded. It is time-consuming to start when the project is high. Obviously, there is some big trouble to start the entire project for a small unit test.
In addition, sometimes some external dependencies cannot be retrieved (such as database connection, cache, or interface acquisition problems), andUnit testing aims to test a specific method of a certain class. It tests the functional logic of the method and should not focus on the objects it depends on., in case the methods at each level are called too deep for each other during testing (the method called should be tested in its own unit test), it is equivalent to focusing only on the width of the test method and not on its depth.
Therefore, for external classes or methods used in the method to be tested, you can consider using mock objects to simulate their calls and customize the returned values.This allows unit tests to proceed normally without executing the real call method, this is the principle of mock. Using mock can well separate the method to be tested from its dependencies, reducing the coupling between modules, and is very suitable for scenarios where external resources are difficult to construct or method calls are too deep.
2 Notes on using mock
- Spring is not started when unit testing through mock, then the container will not be loaded and beans will be generated, and the dependencies cannot be obtained through automatic injection. You need to use mock annotation
- If the relevant functions of the test are related to spring (such as AOP), then SpringbootTest is still necessary
- When mock is enabled, it can be regarded as starting a normal Java project. The code that can be run in a normal project can also be run in a mock.
- During unit testing, not all external methods called or used external classes require mock. For example, entity classes can be directly new, and tool class methods can be run directly (tool classes that do not have external dependencies, such as tool classes used for data format processing, usually call their static methods). There is no need to use mock.
3 mock usage process
3.1 Pre-test configuration
Using mock first, you need to import the relevant dependencies in the pom file, and then you can use@Mock
Or the mock method of the Mockito library to generate mock objects if used@Mock
You need to add the following annotations firstorCode:
- Add before the test class
@RunWith()
or@ExtendWith()
- Add the following method to the test class:
//Preparation before testing, you can perform some basic configuration here@BeforeEach void setUp(){ (this); //Open mock and use it with @Mock}
3.2 Inject the class to be tested and simulate the variables used in it
If the test class isUserService
, then in its test classUserServiceTest
Annotations are required@InjectMocks
injectionUserService
The function of this annotation is to automatically create and inject an instance of a class, and mark it as@Mock
The dependency injects into this instance, note that@InjectMocks
Only class can be injected.
@InjectMocks UserService userService;
3.2.1 Simulate member variables
Member variables of the class to be tested need to be used@Mock
Annotation injection, cannot be passed()
The method generates a mock object. The member mock object generated by the mock() method will report a null pointer error when executed, and the mock object is not really generated.
likeUserService
There are member variables inUserMapper
, then use the following code injection:
@Mock UserMapper
Simply put, it is toUserService
All member variables in it are copied directly toTest
and then@Autowired
Change to@Mock
Just do it.
3.2.2 Simulating static objects
When a static method of a certain class needs to be called in the code, it is necessary to usemockStatic()
The method generates a simulated static object, if the following code exists in the method to be tested:
("A_END"); //DictUtilThere are some external dependencies in the class,Therefore, simulation is needed
Then first, it needs to be generatedDictUtil
Simulated static objects:
mockStatic(); // Both upper and lower writing methods are OKMockedStatic<DictUtil> dictUtilMockedStatic = mockStatic();
3.2.3 Simulate normal variables
For some intermediate variables generated in the code, and when you need to use these variables for method calls in the later stage, you need to use mock to simulate the intermediate variables first, such as if there is code in the method to be tested:
().getName("12345");
Then you need to simulate it firstUserCache
An example ofWhen driving piles, you cannot pile them simultaneously for continuously called methods, and you need to create intermediate variables.), can be used at this time@Mock
Or mock method:
// 1. Inject as a test class member variable@Mock UserCache userCache; // 2. Directly simulate in the test methodUserCache userCache = ();
3.3 Piling simulation method call behavior
3.3.1 Non-static method pile driving
Piling means simulating the parameter transmission of the called method and setting the return value, the number of parameters and return values need to correspond to the original method. After piling, it will not be called, and the execution results of the real method will be ignored. It is generally used().thenReturn()
Perform pile driving.
If the code exists in the test class method:
User user = ("0000");
Then the pile driving in the test method can be:
//Entity class does not require mock, it can be generated directly, and then assign values using the setter methodUser user = new User(); // 1. The pile driving parameter is fixed to "0000". The pile driving failure is when the parameter in the source code call is not "0000".(("0000")).thenReturn(user) // 2. For the robustness of the code, the () method is usually used to receive parameters, and different types are specified according to different xxx. As long as any parameter of this type is a stake, you can drive((())).thenReturn(user)
3.3.2 Static method of pile driving
With original code().getName("12345");
As an example, there are two ways to write static pile driving:
1) Ordinary writing
UserCache userCache = (); mockStatic(); (()).thenReturn(userCache); ((())).thenReturn("TestName");
2) Lambda writing method - static method call without parameters
UserCache userCache = (); MockedStatic<UserCache> userCacheMockedStatic = mockStatic(); //1. When there is no parameter in the static method, you can use :: to make method calls(UserCache::getInstance).thenReturn(userCache); //2. You can also use lambda writing method and use . to call it(() -> ()).thenReturn(userCache); //3. The following writing method cannot be used://(()).thenReturn(userCache); ((())).thenReturn("TestName");
3) Lambda writing method - static method call with parameters
If the abovegetInstance
The method has an int parameter, so you can use the normal writing method in 1) to perform pile driving, or use the method of 2)-2, and then use()
Pass the parameters, but cannot use the writing method of 2)-1 or 2)-3.
3.3.3 Maven test static simulation error problem
There is no problem when running a test separately, but when executing the test collectively, the following error will appear:
‘static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered’
This is because more than two test methods use the same static mock object, such as both tests haveMockedStatic<UserCache> userCacheMockedStatic = mockStatic()
, while static mock objects are not allowed to be public, so each static mock object needs to be closed after it is executed, so the next method can continue to use it, so you need to write at the end of each Test:()
。
becauseMockedStatic<T>
The parent interface inheritsAutoCloseable
Interface, so you can use the try-resources statement to achieve automatic closing of resources:
try( // mockStatic(); // java8 does not support this writing method MockedStatic<UserCache> userCacheMockedStatic = mockStatic(); // ...Other static simulation object declaration){ //Other test code}
You can also choose to use try-catch-finally to manually close it in the finally statement:
try( MockedStatic<UserCache> userCacheMockedStatic = mockStatic(); // …Other test codes)finally{ (); }
3.4 Assertion and judgment test results
To detect the results of the test, an assertion is requiredAssertions
Make judgments and according to actual test requirements:
User user = ("123"); User expectUsr = new User("123"); (expectUsr ,user)://Judge whether the test result user is consistent with the expected object(user)://Judge test resultsuserIs it not empty
This is the article about the unit testing process of using the Mockito library in the Java Springboot backend. For more related Springboot Mockito unit testing content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!