Configuration Introduction

Introduction to the specific use of MockUp

To tell how to use a MockUp, we first give the business class, this class has a static, final, private, public and other methods.

/** * a normal business class with various qualified methods */
@Getter
public class AnNormalService {
    /** * static variable */
    private static int staticInt;
    /** * member variable */
    private int anInt;

    /** * static code block */
    static {
        setStaticInt(200);
    }

    /** * constructor **@param anInt
     */
    public AnNormalService(int anInt) {
        this.anInt = anInt;
    }

    /** * static method **@param num
     */
    public static void setStaticInt(int num) {
        staticInt = num;
    }

    public static int getStaticInt(a) {
        return staticInt;
    }

    /** * common method **@return* /
    public int publicMethod(a) {
        return this.privateMethod();
    }

    /** * private method **@return* /
    private int privateMethod(a) {
        return anInt;
    }

    /** * final method **@return* /
    public final int finalMethod(a) {
        return this.protectedMethod();
    }

    /** * protected method **@return* /
    private int protectedMethod(a) {
        returnanInt; }}Copy the code

Mock demonstrations of various qualified type methods

Mocks of static code blocks

The mock method corresponding to the static code block is called $clinit() and has no arguments

public class AnNormalServiceMockUpTest {
    @BeforeAll
    static void setup(a) {
        new MockUp<AnNormalService>() {
            /** * Mock a static block of code
            @Mock
            void $clinit() {
                AnNormalService.setStaticInt(300); }}; }@displayName (" Validate static code block mock")
    @Test
    void test1(a) {
        int anInt = AnNormalService.getStaticInt();
        Assertions.assertEquals(300, anInt); }}Copy the code

Mock the constructor

To mock the constructor, use the method name $init. The Invocation is the same as the constructor. To reference the mocked instance object, add the Invocation to the Invocation list.

public class AnNormalServiceMockUpTest {
    
    @displayName (" Mock the constructor ")
    @Test
    void test2(a) {
        new MockUp<AnNormalService>() {
            @Mock
            void $init(Invocation inv, int anInt) {
                AnNormalService self = inv.getTarget();
                /** * anInt = input value + 200 */
                self.setAnInt(anInt + 200); }};int anInt = new AnNormalService(200).publicMethod();
        Assertions.assertEquals(400, anInt); }}Copy the code

Mock Public and final methods

Mocking arbitrary methods is easy. You can change the behavior of a method by defining a function of the same name in a MockUp class with the same input type.

public class AnNormalServiceMockUpTest {
    
    @displayName (" Mock public and final methods ")
    @Test
    void test3(a) {
        new MockUp<AnNormalService>() {
            @Mock
            int publicMethod(a) {
                return 5;
            }

            @Mock
            int finalMethod(a) {
                return 6; }}; AnNormalService service =new AnNormalService(400);
        Assertions.assertEquals(5, service.publicMethod());
        Assertions.assertEquals(6, service.finalMethod());
    }


    @displayName (" Mock private and protected methods ")
    @Test
    void test4(a) {
        new MockUp<AnNormalService>() {
            @Mock
            int privateMethod(a) {
                return 9;
            }

            @Mock
            int protectedMethod(a) {
                return 11; }}; AnNormalService service =new AnNormalService(400);
        Assertions.assertEquals(9, service.publicMethod());
        Assertions.assertEquals(11, service.finalMethod());
    }   
 
    @displayName (" Mock on static methods ")
    @Test
    void test5(a) {
        new MockUp<AnNormalService>() {
            @Mock
            int getStaticInt(a) {
                return 178; }}; Assertions.assertEquals(178, AnNormalService.getStaticInt()); }}Copy the code

Use the Invocation to point to the mock instance object

FluentMock provides the Invocation type, which can be used as the first argument to a mock method. The following method is provided for mocking:

The /** * Invocation type can be the first argument to the mock method */
public abstract class Invocation {
    /** * Execute the original logic of the method **@paramThe arguments passed to the method by args (which can be raw inputs or tampered with in mock logic) */
    public <T> T proceed(Object... args);

    /** * The logic before the mock method is executed, with the original input parameter */
    public <T> T proceed(a);

    /** * The static method returns null */ for the current execution instance (that is, equivalent to the built-in Java variable this)
    public abstract <T> T getTarget(a);

    /** * get the method entry list */
    public abstract Object[] getArgs();

    /** * get the index argument, index starts at 0 */
    public Object arg(int index);

    /** * get the index argument, index starts at 0; And cast to type ARG * *@param index
     * @paramClazz strong type */
    public <ARG> ARG arg(int index, Class<ARG> clazz);

    /** * how many times is the return method called */
    public abstract int getInvokedTimes(a);
}
Copy the code

The Invocation is useful in mock and can do a lot of things

Execute original logic

For example, there are business classes as follows:

public class Service {
    private String value = "origin string";

    public String getString(a) {
        return value;
    }

    public void setString(String input) {
        this.value = input; }}Copy the code

The mock method getString, where we want to append information to the original return value, can be mock like the following.

public class InvocationDemo {
    @displayName (" Demo Mock method to execute original method logic ")
    @Test
    void test1(a) {
        new MockUp<Service>() {
            @Mock
            String getString(Invocation inv) {
                String origin = inv.proceed();
                return origin + ", plus mock info."; }}; String result =new Service().getString();
        Assertions.assertEquals("origin string, plus mock info.", result); }}Copy the code

Mock by invocation timing

Sometimes, when we call the same method, we need to return different results depending on the timing of the call. For example, mock the getString above, expecting the first three times to return different values and the rest to return the same value.

public class InvocationDemo {
    @displayName (" Demo mock by call timing ")
    @Test
    void test2(a) {
        new MockUp<Service>() {
            @Mock
            String getString(Invocation inv) {
                switch (inv.getInvokedTimes()) {
                    case 1: return "mock 1";
                    case 2: return "mock 2";
                    case 3: return "mock 3";
                    default: returninv.proceed(); }}}; Service service =new Service();
        assertEquals("mock 1", service.getString());
        assertEquals("mock 2", service.getString());
        assertEquals("mock 3", service.getString());
        assertEquals("origin string", service.getString());
        assertEquals("origin string", service.getString()); }}Copy the code

Asserts an entry or an exit parameter

During testing, except for the need to mock the external interface. Sometimes we also need to evaluate the input and return values to verify that our program is executing properly.

Assert on the input parameter

public class InvocationDemo {
    @displayName (" Assert demonstration on method entry parameters ")
    @Test
    void test3(a) {
        new MockUp<Service>() {
            @Mock
            void setString(Invocation inv, String input) {
                Assertions.assertEquals("Expected value", input); inv.proceed(); }}; Service service =new Service();
        // It passes normally
        service.setString("Expected value");
        // Set other values, which should throw an assertion exception
        Assertions.assertThrows(AssertionError.class, () -> service.setString("Other values")); }}Copy the code

Assert the method output parameter

public class InvocationDemo {
    @displayName (" Assertion demonstration for method output arguments ")
    @Test
    void test4(a) {
        new MockUp<Service>() {
            @Mock
            String getString(Invocation inv) {
                String result = inv.proceed();
                Assertions.assertEquals("origin string", result);
                returnresult; }}; Service service =new Service();
        /**
         * 方法调用正常通过
         */
        service.getString();
        /** * Change the return value to something else, and the method call should throw an assertion exception */
        service.setString("other value"); Assertions.assertThrows(AssertionError.class, () -> service.getString()); }}Copy the code

Mock the specified object instance

The generic Mock we demonstrated above and its methods apply to all instances (because Fluent Mock modifiers bytecode-like implementations), and any calls to the corresponding method go into the logic behind the Mock. But what if we just want to mock specific object instances? Let’s say we have a User class that has two member variables: a list of spouses and children.

@Data
@Accessors(chain = true)
public class User {
    private String name;

    private User spouse;

    private List<User> children = new ArrayList<>();

    public User(String name) {
        this.name = name;
    }

    public User addChild(User child) {
        children.add(child);
        return this;
    }

    /** ** Introduce yourself **@return* /
    public String sayHi(a) {
        StringBuilder buff = new StringBuilder();
        buff.append("I'm ").append(this.getName());
        if (this.spouse ! =null) {
            buff.append(" and my spouse is ").append(this.spouse.getName()).append(".");
        }
        if (!this.children.isEmpty()) {
            buff.append("I have ").append(children.size()).append(" children, ")
                .append(this.children.stream().map(User::getName).collect(Collectors.joining(" and ")))
                .append(".");
        }
        returnbuff.toString(); }}Copy the code

Now let’s show how to mock a given object

public class TargetObjectMockDemo {

    @displayName (" Mock the specified instance ")
    @Test
    void test1(a) {
        /** Has 1 spouse and 2 children **/
        User user = new User("tom")
            .setSpouse(new User("mary"))
            .addChild(new User("mike"))
            .addChild(new User("jack"));
        String hi = user.sayHi();
        /** mock **/
        assertEquals("I'm tom and my spouse is mary.I have 2 children, mike and jack.", hi);
        /** * Mock the spouse */
        new MockUp<User>(user.getSpouse()) {
            @Mock
            String getName(Invocation inv) {
                return "virtual "+ inv.proceed(); }};/** * Mock the child */
        new MockUp<User>(user.getChildren()) {
            @Mock
            String getName(Invocation inv) {
                return "fictitious "+ inv.proceed(); }}; hi = user.sayHi();/** mock **/
        assertEquals("I'm tom and my spouse is virtual mary.I have 2 children, fictitious mike and fictitious jack.", hi); }}Copy the code

Through the example, we see that the “virtual” modifier only acts on the spouse instance getName() method, and the “fictitious” modifier only acts on the child’s getName(), while the user’s own getName() is unaffected.

link

Fluent Mock open source address

Fluent Mybatis open source address