TestableMock tutorial

Posted by pbdude23 on Mon, 31 Jan 2022 11:22:18 +0100

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:

toolprincipleMinimum Mock unitRestrictions on Mock methodsStarting difficultyIDE support
MockitoDynamic agentclassCannot Mock private / static and constructorEasiervery good
SpockDynamic agentclassCannot Mock private / static and constructorMore complexcommonly
PowerMockCustom class loaderclassAny method can be usedMore complexpreferably
JMockitRuntime bytecode modificationclassCannot Mock constructor (new operator)More complexcommonly
TestableMockRuntime bytecode modificationmethodAny method can be usedbe prone tocommonly

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.

Topics: Java Junit Maven