mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-27 12:51:00 +08:00
use mock context to inject extra mock parameters
This commit is contained in:
parent
bb47f5d199
commit
9e393b5ef5
@ -14,6 +14,8 @@
|
||||
> 2. 如遇到"Attempt to access none-static member in mock method"错误,参见[常见问题](https://alibaba.github.io/testable-mock/#/zh-cn/doc/frequently-asked-questions)第8条
|
||||
> 3. 如果有遇到其他任何使用问题,请直接在[Issue](https://github.com/alibaba/testable-mock/issues)中提出,我们将在24小时内回复并处理
|
||||
|
||||
-----
|
||||
|
||||
## 目录结构
|
||||
|
||||
```bash
|
||||
|
@ -9,7 +9,7 @@ import java.util.concurrent.Executors;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
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.MOCK_CONTEXT;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
@ -57,7 +57,7 @@ class DemoMockTest {
|
||||
|
||||
@MockMethod
|
||||
private String callFromDifferentMethod(DemoMock self) {
|
||||
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
|
||||
if ("special_case".equals(MOCK_CONTEXT.get("case"))) {
|
||||
return "mock_special";
|
||||
}
|
||||
switch (SOURCE_METHOD) {
|
||||
@ -105,11 +105,13 @@ class DemoMockTest {
|
||||
|
||||
@Test
|
||||
void should_able_to_get_test_case_name() throws Exception {
|
||||
MOCK_CONTEXT.put("case", "special_case");
|
||||
// synchronous
|
||||
assertEquals("mock_special", demoMock.callerOne());
|
||||
// asynchronous
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
|
||||
verify("callFromDifferentMethod").withTimes(2);
|
||||
MOCK_CONTEXT.remove("case");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
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.MOCK_CONTEXT
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.testable.demo.model.ColorBox
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@ -46,7 +46,7 @@ internal class DemoMockTest {
|
||||
|
||||
@MockMethod
|
||||
private fun callFromDifferentMethod(self: DemoMock): String {
|
||||
return if (TEST_CASE == "should_able_to_get_test_case_name") {
|
||||
return if (MOCK_CONTEXT["case"] == "special_case") {
|
||||
"mock_special"
|
||||
} else {
|
||||
when (SOURCE_METHOD) {
|
||||
@ -97,6 +97,7 @@ internal class DemoMockTest {
|
||||
|
||||
@Test
|
||||
fun should_able_to_get_test_case_name() {
|
||||
MOCK_CONTEXT["case"] = "special_case"
|
||||
// synchronous
|
||||
assertEquals("mock_special", demoMock.callerOne())
|
||||
// asynchronous
|
||||
@ -104,5 +105,6 @@ internal class DemoMockTest {
|
||||
demoMock.callerOne()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").withTimes(2)
|
||||
MOCK_CONTEXT.remove("case")
|
||||
}
|
||||
}
|
||||
|
@ -15,18 +15,17 @@ import java.util.Iterator;
|
||||
*/
|
||||
abstract public class BaseClassHandler implements Opcodes {
|
||||
|
||||
protected static final String TESTABLE_MARK_FIELD = "__testable";
|
||||
|
||||
protected boolean wasTransformed(ClassNode cn) {
|
||||
protected boolean wasTransformed(ClassNode cn, String refName, String refDescriptor) {
|
||||
Iterator<FieldNode> iterator = cn.fields.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
if (TESTABLE_MARK_FIELD.equals(iterator.next().name)) {
|
||||
if (refName.equals(iterator.next().name)) {
|
||||
// avoid duplicate injection
|
||||
LogUtil.verbose("Duplicate injection found, ignore " + cn.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cn.fields.add(new FieldNode(ACC_PRIVATE, TESTABLE_MARK_FIELD, "I", null, null));
|
||||
// TODO: `ACC_STATIC` should be removed in v0.5 to shorten the life cycle of this variable
|
||||
cn.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, refName, refDescriptor, null, null));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ import java.util.Set;
|
||||
*/
|
||||
public class SourceClassHandler extends BaseClassHandler {
|
||||
|
||||
private static final String TESTABLE_MARK_FIELD = "__testable";
|
||||
|
||||
private final List<MethodInfo> injectMethods;
|
||||
private final Set<Integer> invokeOps = new HashSet<Integer>() {{
|
||||
add(Opcodes.INVOKEVIRTUAL);
|
||||
@ -36,7 +38,7 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
*/
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if (wasTransformed(cn)) {
|
||||
if (wasTransformed(cn, TESTABLE_MARK_FIELD, "I")) {
|
||||
return;
|
||||
}
|
||||
Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>();
|
||||
|
@ -19,14 +19,19 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool";
|
||||
private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
|
||||
private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil";
|
||||
private static final String CLASS_TESTABLE_CONTEXT = "com/alibaba/testable/agent/model/TestableContext";
|
||||
private static final String REF_TESTABLE_CONTEXT = "_testableContextReference";
|
||||
private static final String FIELD_TEST_CASE = "TEST_CASE";
|
||||
private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD";
|
||||
private static final String FIELD_MOCK_CONTEXT = "MOCK_CONTEXT";
|
||||
private static final String FIELD_PARAMETERS = "parameters";
|
||||
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_RECORD_MOCK_INVOKE = "recordMockInvoke";
|
||||
private static final String SIGNATURE_CURRENT_TEST_CASE_NAME = "(Ljava/lang/String;)Ljava/lang/String;";
|
||||
private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;";
|
||||
private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;Z)V";
|
||||
private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;";
|
||||
|
||||
/**
|
||||
* Handle bytecode of test class
|
||||
@ -34,15 +39,29 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
*/
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if (wasTransformed(cn)) {
|
||||
if (wasTransformed(cn, REF_TESTABLE_CONTEXT, ClassUtil.toByteCodeClassName(CLASS_TESTABLE_CONTEXT))) {
|
||||
return;
|
||||
}
|
||||
for (MethodNode mn : cn.methods) {
|
||||
handleMockMethod(cn, mn);
|
||||
handleInstruction(cn, mn);
|
||||
if (mn.name.equals(ConstPool.CONSTRUCTOR)) {
|
||||
initMockContextReference(cn, mn);
|
||||
} else {
|
||||
handleMockMethod(cn, mn);
|
||||
handleInstruction(cn, mn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initMockContextReference(ClassNode cn, MethodNode mn) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(new TypeInsnNode(NEW, CLASS_TESTABLE_CONTEXT));
|
||||
il.add(new InsnNode(DUP));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, CLASS_TESTABLE_CONTEXT, "<init>", "()V", false));
|
||||
il.add(new FieldInsnNode(PUTSTATIC, cn.name, REF_TESTABLE_CONTEXT,
|
||||
ClassUtil.toByteCodeClassName(CLASS_TESTABLE_CONTEXT)));
|
||||
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
||||
}
|
||||
|
||||
private void handleMockMethod(ClassNode cn, MethodNode mn) {
|
||||
if (isMockMethod(mn)) {
|
||||
toPublicStatic(cn, mn);
|
||||
@ -121,7 +140,8 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
|
||||
private boolean isTestableUtilField(FieldInsnNode fieldInsnNode) {
|
||||
return fieldInsnNode.owner.equals(CLASS_TESTABLE_TOOL) &&
|
||||
(fieldInsnNode.name.equals(FIELD_TEST_CASE) || fieldInsnNode.name.equals(FIELD_SOURCE_METHOD));
|
||||
(fieldInsnNode.name.equals(FIELD_TEST_CASE) || fieldInsnNode.name.equals(FIELD_SOURCE_METHOD) ||
|
||||
fieldInsnNode.name.equals(FIELD_MOCK_CONTEXT));
|
||||
}
|
||||
|
||||
private AbstractInsnNode[] replaceTestableUtilField(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions,
|
||||
@ -134,6 +154,10 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
} else if (FIELD_SOURCE_METHOD.equals(fieldName)) {
|
||||
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_CURRENT_SOURCE_METHOD_NAME,
|
||||
SIGNATURE_CURRENT_SOURCE_METHOD_NAME, false));
|
||||
} else if (FIELD_MOCK_CONTEXT.equals(fieldName)) {
|
||||
il.add(new FieldInsnNode(GETSTATIC, cn.name, REF_TESTABLE_CONTEXT,
|
||||
ClassUtil.toByteCodeClassName(CLASS_TESTABLE_CONTEXT)));
|
||||
il.add(new FieldInsnNode(GETFIELD, CLASS_TESTABLE_CONTEXT, FIELD_PARAMETERS, SIGNATURE_PARAMETERS));
|
||||
}
|
||||
if (il.size() > 0) {
|
||||
mn.instructions.insert(instructions[pos], il);
|
||||
|
@ -0,0 +1,13 @@
|
||||
package com.alibaba.testable.agent.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class TestableContext {
|
||||
|
||||
public Map<String, Object> parameters = new HashMap<String, Object>();
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.alibaba.testable.core.tool;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
@ -7,7 +9,9 @@ public class TestableTool {
|
||||
|
||||
/**
|
||||
* Name of current test case method
|
||||
* @deprecated prefer using `MOCK_CONTEXT` to distinguish test cases
|
||||
*/
|
||||
@Deprecated
|
||||
public static String TEST_CASE;
|
||||
|
||||
/**
|
||||
@ -15,4 +19,9 @@ public class TestableTool {
|
||||
*/
|
||||
public static String SOURCE_METHOD;
|
||||
|
||||
/**
|
||||
* Inject extra mock parameters
|
||||
*/
|
||||
public static Map<String, Object> MOCK_CONTEXT;
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user