[Java] 2. Unit Test Basic Usage
[Java] 2. Unit Test Basic Usage
Mockito Basic Usage
In unit testing, many tests (except Util classes) need to mock some services to ensure only the current logic being tested is actually tested.
Specifically, you need to first mock an object, then mock the methods of this object, and then you can use the mocked methods to test the logic you want to test.
Mock Objects
First, you need to declare the interfaces/implementation classes that need to be mocked in the Test class. For example:
|
|
Sometimes, you also need to manually mock something directly, for example, when you need to mock Redis operations, you can:
|
|
Note: Don’t mock primitive types like int, long, etc.
There’s another way using @SpyBean
. This will be skipped for now and introduced in later sections.
Mock Methods
Assume there’s an interface:
|
|
With userService already mocked:
|
|
We can mock the add method so that any parameter passed will directly return 100.
|
|
You can also do it this way:
|
|
However, for the same service, don’t mix these two approaches, otherwise sometimes the second mock may become ineffective.
When you find that your mocked methods are ineffective, or you encounter some mysterious errors, please use one mocking approach consistently. If it still doesn’t work, try switching to the other approach.
When you need to mock a void method, you can:
|
|
If you need to simulate error conditions, you can:
|
|
When you need to mock specific data, like userId = 1 has a user, userId = 2 has no user, you can mock like this:
|
|
Mock has more general approaches. If you want to return data when userId < 10, and not return data in other cases:
|
|
Or:
|
|
Note that doAnswer can also mock void return values. Suppose I now want to mock Redis operations:
|
|
In this example, when calling Redis set, we also mock get to return the value that was just set. This simulates Redis operations.
When you mock overloaded methods, you may encounter errors, for example:
|
|
At this point, find(any()) can match both methods, which will cause problems. The solution is:
|
|
When using any(), be careful:
- Don’t use any() to represent long, int values, otherwise you’ll get NPE.
- You can specifically use any(class) for specific inputs, like any(LocalDateTime.class) can represent any LocalDateTime parameter.
When using mock, you can also use doCallRealMethod()
or thenCallRealMethod()
to call the original implementation, but I personally don’t recommend this approach as it generally causes various problems. Specific solutions will be covered later.
Testing Results
Generally, after you’ve mocked all called methods and used assert tools to verify the output, the program is basically running as expected.
|
|
For example, if you’re testing this reg method, Mockito tools can verify whether userService.add()
method was actually called once:
|
|
However, sometimes you need to check other things, like the call parameters of your mocked methods. This is when you need Mockito tools:
|
|
There’s also a simpler approach:
|
|
SpyBean Example
In the above examples, we used MockBean, which is the most widely used mocking approach. But sometimes, tests need to go deep into implementation classes, modify some logic, and then test. This is when SpyBean is needed.
MockBean is equivalent to completely mocking a class, where all methods in this class are mocked and cannot be called directly without first mocking them. SpyBean is equivalent to taking a real implementation and then only mocking part of its methods.
For example, suppose in the IUserService implementation, there’s this code:
|
|
At this point, the remove method calls the find method. If you want to test this remove method, mocking the entire implementation class is obviously not feasible. If you don’t mock, the return value of this find method is uncontrollable. If you directly put data in the database, that’s not a complete unit test because you’re using external services.
This is when SpyBean should be used. You can mock only the find method, then directly call remove to execute the test.
|
|
Similarly, there are some logic that’s not easy to test, like:
|
|
You need to test that this while loop runs according to your logic, but this sleep method will actually sleep, making the entire test extremely troublesome. Using SpyBean solves this well.
|
|
Similarly, when you need to test some time-related operations, this logic is critical but closely related to current time, SpyBean is also needed.
|
|
Mock operation:
|
|
Test Case Specifications
Test Case Naming Rules
For method startTask under test, there might be the following test cases:
- startTask_noPerm
- startTask_banned
- startTask_succeed
- startTask_limited
- …
javaguide.html#s5.2.3-method-names
Test Case Comment Rules
- 1 Test scenario description
- 2 Expected results, actual results
Test Scope Rules
- Use Mockito to isolate test boundaries
- Use Postman for integration testing
- Controller layer should also have test cases
Code Coverage
- Maximize code coverage
Test Case Specifications
- Build test data
- Use test data to build mock methods
- Execute methods
- Verify mock results
Key Points
- Unit test cases should embody the concept of
unit
. When building test data, attention should be paid to sufficient unit isolation betweenbuild data
- Simply put, unit test code also needs to be elegant enough with high extensibility, so that when business modifications occur later, testing can be better extended