【发布时间】:2020-06-17 13:14:41
【问题描述】:
我一直在从事一个项目,我想动态拦截对外部依赖项的调用并在运行时模拟它们的返回。我已经开始实施一个小的概念证明,你可以在这里找到:
https://gitlab.com/connorbutch/mock-manager
本质上,这个项目所做的是:
- 找到所有实现标记接口的类 (https://gitlab.com/connorbutch/mock-manager/-/blob/master/src/main/java/com/connor/ws/ExternalDependency.java)
- 用单例动态注册它们中的每一个 (https://gitlab.com/connorbutch/mock-manager/-/blob/master/src/main/java/com/connor/mock/MockManagerExternalDependencyImpl.java)
- 有一个拦截器,我想拦截对使用 MockForIntegrationTest 注释 (https://gitlab.com/connorbutch/mock-manager/-/blob/master/src/main/java/com/connor/mock/MockForIntegrationTest.java) 注释的方法的所有调用
- 在测试期间调用该方法时,拦截器会将调用路由到 bean
但是,我遇到的错误是:
Exception in thread "main" javax.enterprise.inject.CreationException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at java.base/java.lang.Class.newInstance(Class.java:584)
at org.jboss.weld.security.NewInstanceAction.run(NewInstanceAction.java:33)
//way more logs here
这是我的 beans.xml 的内容:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" bean-discovery-mode="all" version="2.0">
<interceptors>
<class>com.connor.mock.IntegrationTestInterceptor</class>
</interceptors>
这里是拦截器类:
package com.connor.mock;
import java.lang.reflect.Method;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import com.connor.ws.ExternalDependency;
/**
* This class holds the interceptors
* @author connor
*
*/
@Interceptor
@MockForIntegrationTest
public class IntegrationTestInterceptor {
private final MockManagerExternalDependencyImpl mockManager;
/**
* Cdi-enabled constructor
* @param mockManager
*/
@Inject
public IntegrationTestInterceptor(MockManagerExternalDependencyImpl mockManager) {
this.mockManager = mockManager;
}
/**
* This method intercepts external calls for integration tests, and passes them to the mock
* @param context
* @throws Exception
*/
@AroundInvoke
public Object interceptAndMockExternalCall(InvocationContext context) throws Exception {
System.out.println("Inside InterceptorHolder.interceptAndMockExternalCall");
//get what we can from the
Class<?> clazz = context.getTarget().getClass();
String methodName = context.getMethod().getName();
Object[] argsForMethod = context.getParameters();
//loop through parameters and create an array of their types for use with reflection later
Class<?>[] parameterTypes = new Class<?>[argsForMethod.length];
for(int i = 0; i < argsForMethod.length; ++i) {
parameterTypes[i] = argsForMethod[i].getClass();
}
//this means that this annotation was used on an incorrect method/class that does not implement our marker interface
if(!ExternalDependency.class.isAssignableFrom(clazz)) {
throw new Exception("Please only annotate methods with mockforintegration test interceptor binding if the class implements the marker interface ApiClient");
}
//we know this is assignable, so we can cast here
//get our mock from the singleton mock manager
@SuppressWarnings("unchecked") //we know this is safe to cast because of the assignable from check above
Object mockToInvoke = mockManager.getMockedInstance((Class<? extends ExternalDependency>) clazz);
//get the method to invoke on the mock from the method name and parameters
Method methodToInvoke = mockToInvoke.getClass().getMethod(methodName, parameterTypes);
Object returnValue;
try {
returnValue = methodToInvoke.invoke(mockToInvoke, argsForMethod);
}catch(Exception e) {
//TODO
//NOTE: break out into separate catch blocks for each type -- original method throws exception, then rethrow
throw new Exception("There was an error using reflection to invoke method on mock", e);
}
//log success here
System.out.println(returnValue);
return returnValue;
}
}
这是使用拦截器的类:
package com.connor.ws;
import com.connor.mock.MockForIntegrationTest;
public class ExternalDependencyPolicyClientImpl implements ExternalDependency {
/**
*
*/
private static final long serialVersionUID = -8845199637628066567L;
/**
* This is a dummy method
* @return
*/
@MockForIntegrationTest
public String dummy() {
return "dummy";
}
}
这是我的 pom 的副本:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.connor</groupId>
<artifactId>mock-manager</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>This project demonstrates how to use mock manager for integration tests involving outside web services.</description>
<properties>
<!-- dependency versions, please keep in alphabetical order -->
<cdi.version>2.0.SP1</cdi.version>
<inject.version>1</inject.version>
<interceptor.version>1.2.2</interceptor.version>
<mockito.version>3.1.0</mockito.version>
<weld.version>3.1.3.Final</weld.version>
<!-- use java 8: TODO make use java 14 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>${cdi.version}</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>${inject.version}</version>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>${interceptor.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
<version>${weld.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
</dependency>
</dependencies>
欢迎提出任何想法。
附:代码在没有拦截器(包括 cdi)的情况下工作
【问题讨论】:
标签: cdi interceptor weld