preface
TestableMock is Alibaba's unit testing tool. It supports Mock public methods, private methods, static methods, construction methods and other functions. It is easy and fast to use
At present, the main Mock tools are Mockito, Spock, PowerMock and JMockit. The basic differences are as follows:
tool | principle | Minimum Mock unit | Restrictions on Mock methods | Starting difficulty | IDE support |
---|---|---|---|---|---|
Mockito | Dynamic agent | class | Cannot Mock private / static and constructor | Easier | very good |
Spock | Dynamic agent | class | Cannot Mock private / static and constructor | More complex | commonly |
PowerMock | Custom class loader | class | Any method can be used | More complex | preferably |
JMockit | Runtime bytecode modification | class | Cannot Mock constructor (new operator) | More complex | commonly |
TestableMock | Runtime bytecode modification | method | Any method can be used | be prone to | commonly |
1, Rely on
<!-- Unified configuration version --> <properties> <testable.version>0.7.2</testable.version> </properties> <!-- Add dependency --> <dependencies> <dependency> <groupId>com.alibaba.testable</groupId> <artifactId>testable-all</artifactId> <version>${testable.version}</version> <scope>test</scope> </dependency> </dependencies> <!-- Add plug-in configuration --> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine> </configuration> </plugin> </plugins> </build>
Official website documents: https://alibaba.github.io/testable-mock/#/
2, Examples
There are three ways to create a Mock container
- Annotation: create a class under any package path with arbitrary class name, and use annotation to declare Mock class in the test class
- Inner class: create a static inner class in the test class. The class name must be Mock
- Independent class: create a class under the same package path of the test class. The class name is the tested class name + Mock
Priority: annotation - > internal class - > independent class
Inner class
// Test class class TestServiceTest { // Mock container public static class Mock { @MockInvoke(targetClass = TestService.class) private String privateMethod(String str) { return "456"; } } // test method @ParameterizedTest @CsvSource({ "123", "456" }) void testPublicMethod(String str) { String res = testService.publicMethod(str); assertEquals("456", res); } // The returned results of normal call should be 123 and 456 // After Mock, the returned result is 456 twice } // Tested class public class TestService { public String publicMethod(String str) { return privateMethod(str); } private String privateMethod(String str) { return str; } }
// Independent Mock class public class ServiceMock { @MockInvoke(targetClass = TestService.class) private String privateMethod(String str) { return "456"; } // Other Mock methods } // Test class // Declare the Mock class used with annotations @MockWith(ServiceMock.class) class TestServiceTest { // Test code }
Independent class
// Independent Mock class public class TestServiceMock { @MockInvoke(targetClass = TestService.class) private String privateMethod(String str) { return "456"; } // Other Mock methods } // Test class class TestServiceTest { // Test code } // Tested class class TestService{}
3, Other usage
1. Reuse
If some methods need to be mocked frequently, there is no need to write Mock methods repeatedly. Just create an independent Mock class. These classes can be used by annotation or inheritance in the test class. The use methods are as follows:
Notes:
The method of using annotation refers to the method in the main reference example Use Mock method of independent class
Succession:
class TestServiceTest { // The inner Mock class inherits the independent Mock class public static class Mock extends ServiceMock { // Custom Mock method } }
If the inherited class has the same Mock method as the inner class, the Mock method will be overridden
2. Check
In testing, it is sometimes necessary to verify whether the method call parameters are correct and whether the method call times are correct
TestableMock provides verifier and matcher to realize this function
Basic calibrator
-
with(Object... args) → verify whether the method has been called by the specified parameter
-
withInOrder(Object... args) → if the specified method is called multiple times, match it in turn according to the actual call order
-
withTimes(int expectedCount) → verify whether the method has been called a specified number of times, ignoring the check of the call parameters
-
without(Object... args) → the verification method has never been called with the specified parameters
-
times(int count) → use after the with() or withInOrder() method to verify that the method has been called for the specified number of times by parameters with the same conditions
Basic matcher
- any() → match any value, including Null
- anyString() → match any string
- anyNumber() → match any number (integer or floating point number)
Use example:
InvocationVerifier.verifyInvoked("getJSONArray").with(InvocationMatcher.anyString());
More ways: https://alibaba.github.io/testable-mock/#/zh-cn/doc/invoke-matcher
3. Test private methods
Some private methods are called indirectly through public methods, which can not achieve high coverage. TestableMock supports direct testing of private methods
The PrivateAccessor tool class can directly access private methods,
String res = PrivateAccessor.invoke(new TestService(), "privateMethod", "123");
More ways: https://alibaba.github.io/testable-mock/#/zh-cn/doc/private-accessor
4. Construction data
public class Article { private Integer id; private String name; private String url; private Date createDate; } // Construction data Article newInstance = OmniConstructor.newInstance(Article.class); // Structural results // Article(id=0, name=, url=, createDate=Wed Jan 26 09:42:31 CST 2022)
The constructed data is the default value of the basic type. For example, the Integer type data is 0, the String type data is "", and the time type data is the current time
Document address: https://alibaba.github.io/testable-mock/#/zh-cn/doc/omni-constructor
5. Distinguish the call source in Mock method
In a test class, there may be requirements for the same Mock method to return different results in different scenarios. TestableMock can be used through testabletool SOURCE_ The method variable identifies the method under test that enters the Mock method
// Mock method @MockInvoke(targetClass = TestService.class) private String privateMethod(String str) { return TestableTool.SOURCE_METHOD; } // test method @Test void testPublicMethodV1() { String res = testService.publicMethod("str"); assertEquals("publicMethod", res); } @Test void testPublicMethodV2() { String res = testService.publicMethodV2("str"); assertEquals("publicMethodV2", res); } // Tested method public String publicMethod(String str) { return privateMethod(str); } public String publicMethodV2(String str) { return privateMethod(str); } public List<Article> getAllArticle(){ ArticleExample articleExample = new ArticleExample(); articleExample.createCriteria().andIdIsNotNull(); return articleMapper.selectByExample(articleExample); }
You can also use testabletool Mock_ The context variable injects "additional context parameters" into the Mock method to distinguish different call scenarios. Use example:
// Mock method @MockInvoke(targetClass = TestService.class) private String privateMethod(String str) { // Through mock_ The value of context determines the result to be returned if("v1".equals(TestableTool.MOCK_CONTEXT.get("public"))) { return "456"; } return "789"; } // The test method is mock_ Assignment of context variable @Test void testPublicMethodV1() { TestableTool.MOCK_CONTEXT.put("public", "v1"); String res = testService.publicMethod("str"); assertEquals("456", res); TestableTool.MOCK_CONTEXT.put("public", "v2"); res = testService.publicMethod("str"); assertEquals("789", res); }
4, Precautions
1. Eclipse launch configuration
Because the unit test executor built in Eclipse completely ignores POM XML file, so if you need to use the Mock function, you need to make additional configuration.
Official website document address: https://alibaba.github.io/testable-mock/#/zh-cn/doc/use-in-ide
Right click - > Run - > Run configurations... - > arguments - > VM arguments
// Add the following parameters in the running configuration -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar // You need to replace it with your maven warehouse address and dependent version. Examples of replacement results: -javaagent:D:\workspace\Maven\repository/com/alibaba/testable/testable-agent/0.7.2/testable-agent-0.7.2.jar
2. Renew
TestableMock from 0.6 X updated to 0.7 After X, the names of some annotations were changed: @ MockMethod and @ MockConstructor were renamed @ MockInvoke and @ MockNew, respectively.