mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-09 20:00:21 +08:00
implement verify mock invoke times method
This commit is contained in:
parent
ee21025dbb
commit
e2d9c754e0
@ -7,8 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.TEST_CASE;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@EnablePrivateAccess
|
||||
@ -70,16 +69,21 @@ class DemoServiceTest {
|
||||
@Test
|
||||
void should_able_to_test_new_object() throws Exception {
|
||||
assertEquals("mock_something", demoService.newFunc());
|
||||
verify("createBlackBox").times(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_test_member_method() throws Exception {
|
||||
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"));
|
||||
verify("innerFunc").times(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_test_common_method() throws Exception {
|
||||
assertEquals("trim_string__sub_string__false", demoService.commonFunc());
|
||||
verify("trim").times(1);
|
||||
verify("sub").times(1);
|
||||
verify("startsWith").times(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -89,6 +93,7 @@ class DemoServiceTest {
|
||||
// asynchronous
|
||||
assertEquals("mock_one_mock_others",
|
||||
Executors.newSingleThreadExecutor().submit(() -> demoService.callerOne() + "_" + demoService.callerTwo()).get());
|
||||
verify("callFromDifferentMethod").times(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -97,6 +102,7 @@ class DemoServiceTest {
|
||||
assertEquals("mock_special", demoService.callerOne());
|
||||
// asynchronous
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoService.callerOne()).get());
|
||||
verify("callFromDifferentMethod").times(2);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ package com.alibaba.testable.demo
|
||||
import com.alibaba.testable.core.accessor.PrivateAccessor
|
||||
import com.alibaba.testable.core.annotation.EnablePrivateAccess
|
||||
import com.alibaba.testable.core.annotation.TestableMock
|
||||
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
|
||||
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
|
||||
import com.alibaba.testable.core.tool.TestableTool.*
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.Executors
|
||||
@ -57,16 +56,21 @@ internal class DemoServiceTest {
|
||||
@Test
|
||||
fun should_able_to_test_new_object() {
|
||||
assertEquals("mock_something", demoService.newFunc())
|
||||
verify("createBlackBox").times(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_test_member_method() {
|
||||
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"))
|
||||
verify("innerFunc").times(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_test_common_method() {
|
||||
assertEquals("trim_string__sub_string__false", demoService.commonFunc())
|
||||
verify("trim").times(1)
|
||||
verify("sub").times(1)
|
||||
verify("startsWith").times(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -77,6 +81,7 @@ internal class DemoServiceTest {
|
||||
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
|
||||
demoService.callerOne() + "_" + demoService.callerTwo()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").times(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -87,5 +92,6 @@ internal class DemoServiceTest {
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
|
||||
demoService.callerOne()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").times(2)
|
||||
}
|
||||
}
|
||||
|
2
pom.xml
2
pom.xml
@ -9,8 +9,8 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>testable-agent</module>
|
||||
<module>testable-core</module>
|
||||
<module>testable-agent</module>
|
||||
<module>testable-maven-plugin</module>
|
||||
<module>demo</module>
|
||||
</modules>
|
||||
|
@ -20,7 +20,9 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD";
|
||||
private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName";
|
||||
private static final String METHOD_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName";
|
||||
private static final String METHOD_COUNT_MOCK_INVOKE = "countMockInvoke";
|
||||
private static final String SIGNATURE_TESTABLE_UTIL_METHOD = "(Ljava/lang/Object;)Ljava/lang/String;";
|
||||
private static final String SIGNATURE_INVOKE_COUNTER_METHOD = "()V";
|
||||
private static final Map<String, String> FIELD_TO_METHOD_MAPPING = new HashMap<String, String>() {{
|
||||
put(FIELD_TEST_CASE, METHOD_CURRENT_TEST_CASE_NAME);
|
||||
put(FIELD_SOURCE_METHOD, METHOD_CURRENT_SOURCE_METHOD_NAME);
|
||||
@ -56,6 +58,7 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
mn.access &= ~ACC_PRIVATE;
|
||||
mn.access &= ~ACC_PROTECTED;
|
||||
mn.access |= ACC_PUBLIC;
|
||||
injectInvokeCounter(mn);
|
||||
} else if (couldBeTestMethod(mn)) {
|
||||
injectTestableRef(cn, mn);
|
||||
}
|
||||
@ -92,6 +95,12 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
return mn.instructions.toArray();
|
||||
}
|
||||
|
||||
private void injectInvokeCounter(MethodNode mn) {
|
||||
MethodInsnNode node = new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_COUNT_MOCK_INVOKE,
|
||||
SIGNATURE_INVOKE_COUNTER_METHOD, false);
|
||||
mn.instructions.insertBefore(mn.instructions.get(0), node);
|
||||
}
|
||||
|
||||
private void injectTestableRef(ClassNode cn, MethodNode mn) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(new VarInsnNode(ALOAD, 0));
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.agent.model;
|
||||
package com.alibaba.testable.agent.tool;
|
||||
|
||||
|
||||
/**
|
@ -3,7 +3,7 @@ package com.alibaba.testable.agent.transformer;
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import com.alibaba.testable.agent.handler.SourceClassHandler;
|
||||
import com.alibaba.testable.agent.handler.TestClassHandler;
|
||||
import com.alibaba.testable.agent.model.ImmutablePair;
|
||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||
import com.alibaba.testable.agent.model.MethodInfo;
|
||||
import com.alibaba.testable.agent.tool.ComparableWeakRef;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.alibaba.testable.core.error;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class VerifyFailedError extends AssertionError {
|
||||
|
||||
public VerifyFailedError(int actualCount, int expectedCount) {
|
||||
super(getErrorMessage(actualCount, expectedCount));
|
||||
}
|
||||
|
||||
private static String getErrorMessage(int actualCount, int expectedCount) {
|
||||
return "\nExpected times : " + expectedCount + "\nActual times : " + actualCount;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.alibaba.testable.core.tool;
|
||||
|
||||
import com.alibaba.testable.core.error.VerifyFailedError;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class InvokeCounter {
|
||||
|
||||
private final int actualCount;
|
||||
|
||||
public InvokeCounter(int actualCount) {
|
||||
this.actualCount = actualCount;
|
||||
}
|
||||
|
||||
public void times(int expectedCount) {
|
||||
if (expectedCount != actualCount) {
|
||||
throw new VerifyFailedError(actualCount, expectedCount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.alibaba.testable.core.tool;
|
||||
|
||||
import com.alibaba.testable.core.util.TestableUtil;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
@ -15,4 +17,14 @@ public class TestableTool {
|
||||
*/
|
||||
public static String SOURCE_METHOD;
|
||||
|
||||
/**
|
||||
* Get counter to check whether specified mock method invoked
|
||||
* @param mockMethodName name of a mock method
|
||||
*/
|
||||
public static InvokeCounter verify(String mockMethodName) {
|
||||
String testClass = Thread.currentThread().getStackTrace()[TestableUtil.INDEX_OF_TEST_CLASS].getClassName();
|
||||
String testCaseName = TestableUtil.currentTestCaseName(testClass);
|
||||
return new InvokeCounter(TestableUtil.getInvokeCount(mockMethodName, testCaseName));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,11 +2,34 @@ package com.alibaba.testable.core.util;
|
||||
|
||||
import com.alibaba.testable.core.constant.ConstPool;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class TestableUtil {
|
||||
|
||||
private static final Map<String, Integer> INVOKE_RECORDS = new HashMap<String, Integer>();
|
||||
private final static String JOINER = "->";
|
||||
/**
|
||||
* [0]Thread -> [1]TestableUtil/TestableTool -> [2]TestClass
|
||||
*/
|
||||
public static final int INDEX_OF_TEST_CLASS = 2;
|
||||
|
||||
/**
|
||||
* Record mock method invoke event
|
||||
*/
|
||||
public static void countMockInvoke() {
|
||||
StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS];
|
||||
String mockMethodName = mockMethodTraceElement.getMethodName();
|
||||
String testClass = mockMethodTraceElement.getClassName();
|
||||
String testCaseName = TestableUtil.currentTestCaseName(testClass);
|
||||
String key = testCaseName + JOINER + mockMethodName;
|
||||
int count = getInvokeCount(mockMethodName, testCaseName);
|
||||
INVOKE_RECORDS.put(key, count + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last visit method in source file
|
||||
* @param testClassRef usually `this` variable of the test class
|
||||
@ -30,8 +53,17 @@ public class TestableUtil {
|
||||
*/
|
||||
public static String currentTestCaseName(Object testClassRef) {
|
||||
Class<?> testClass = testClassRef.getClass();
|
||||
StackTraceElement[] stack = getMainThread().getStackTrace();
|
||||
String testClassName = getRealClassName(testClass);
|
||||
return currentTestCaseName(testClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current test case method
|
||||
* @param testClassName name of current test class
|
||||
* @return method name
|
||||
*/
|
||||
public static String currentTestCaseName(String testClassName) {
|
||||
StackTraceElement[] stack = getMainThread().getStackTrace();
|
||||
for (int i = stack.length - 1; i >= 0; i--) {
|
||||
if (stack[i].getClassName().equals(testClassName)) {
|
||||
return stack[i].getMethodName();
|
||||
@ -40,6 +72,15 @@ public class TestableUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static int getInvokeCount(String mockMethodName, String testCaseName) {
|
||||
String key = testCaseName + JOINER + mockMethodName;
|
||||
Integer count = INVOKE_RECORDS.get(key);
|
||||
if (count == null) {
|
||||
count = 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static String findLastMethodFromSourceClass(String sourceClassName, StackTraceElement[] stack) {
|
||||
for (StackTraceElement element : stack) {
|
||||
if (element.getClassName().equals(sourceClassName)) {
|
||||
|
Loading…
Reference in New Issue
Block a user