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