I am using a custom annotation to get the value of an argument based on its name.
The implementation is based on past examples here but I am getting following error.
Could I get some advice on what I am doing wrong please? Thanks.
The error is being throw when the class ExpressionEvaluator calls the method condition().
Error as follows:
EL1027E: Indexing into type 'ExpressionRootObject' is not supported
org.springframework.expression.spel.SpelEvaluationException: EL1027E:
Indexing into type 'ExpressionRootObject' is not supported
Custom Annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomAnnot {
String name();
String countryCode() default "example";
String[] requestKeys();
}
Root Object
public class ExpressionRootObject {
private final Object object;
private final Object[] args;
public ExpressionRootObject(Object object, Object[] args) {
this.object = object;
this.args = args;
}
public Object getObject() {
return object;
}
public Object[] getArgs() {
return args;
}
}
Expression Evaluator
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
/**
* Create the suitable {#link EvaluationContext} for the specified event handling
* on the specified method.
*/
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
/**
* Specify if the condition defined by the specified expression matches.
*/
public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
// IT FAILS AT THIS CALL.
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
Aspect that tries to evaluate
#Aspect
#Component
public class AspectMocking {
#Around("#annotation(customAnnot)")
public Object getMockedData(ProceedingJoinPoint pjp, CustomAnnot customAnnot) throws Throwable {
try{
//This is the call which eventually fails at the condition() method below.
Long l = getValue(pjp, customAnnot.requestKeys()[0]);
} catch(Exception e){
//
}
return null;
}
private Long getValue(ProceedingJoinPoint joinPoint, String condition) {
return getValue(joinPoint.getTarget(), joinPoint.getArgs(),
joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), condition);
}
private Long getValue(Object object, Object[] args, Class clazz, Method method, String condition) {
if (args == null) {
return null;
}
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.condition(condition, methodKey, evaluationContext, Long.class);
}
}
I am annotating the following method with the custom annotation:
#CustomAnnot(name = "APPLE", requestKeys = {"id", "label"})
public Object get(long id, long label) {
// some code
}
Related
I need to log the values of each object. The type of object may vary every time, i am trying to invoke getters of class using reflection. But i am stuck at a place where i need to reinvoke readData method, if class is a Custom Object. how to get an object to pass in readData(obj) in else block below.
private static void readData(Object resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] allMethods = resp.getClass().getDeclaredMethods();
for (Method m : allMethods) {
if ("get".equalsIgnoreCase(m.getName().substring(0, 3))) {
Class<?> type = m.getReturnType();
if (isWrapperType(type) || type.isPrimitive()) {
System.out.println(m.invoke(resp)) ;
}
else if(Collection.class.isAssignableFrom(type)) {
if(m.getGenericReturnType() instanceof ParameterizedType){
ParameterizedType paramType = (ParameterizedType) m.getGenericReturnType();
System.out.println("List is of type "+(Class<?>) paramType.getActualTypeArguments()[0]);
}
//iterate the object and recall read data with generic type of collection
}
else{
//Problem : need to pass object from type, how do i get this class object, as it should not be any new instance
readData(obj);
}
}
}
}
private static final Set<Class<?>> WRAPPER_TYPES = getWrapperTypes();
public static boolean isWrapperType(Class<?> clazz)
{
return WRAPPER_TYPES.contains(clazz);
}
private static Set<Class<?>> getWrapperTypes()
{
Set<Class<?>> ret = new HashSet<Class<?>>();
ret.add(Boolean.class);
ret.add(Character.class);
ret.add(Byte.class);
ret.add(Short.class);
ret.add(Integer.class);
ret.add(Long.class);
ret.add(Float.class);
ret.add(Double.class);
ret.add(String.class);
ret.add(BigDecimal.class);
ret.add(Number.class);
return ret;
}
This is how BO's look like
Response.java
public class Response {
List<OrderStatusList> orderStatusList;
StatusResponse response;
//getter-setter
}
StatusResponse.java
public class StatusResponse {
protected String type;
protected String message;
// getter-setter
}
OrderStatusList.java
public class OrderStatusList {
Header header;
// getter - setter
}
Header.java
public class Header {
protected String orderNumber;
protected String orderStatus;
protected List<DtOrderStatusResponseList> item;
//getter-setter
}
DtOrderStatusResponseList.java
public class DtOrderStatusResponseList {
protected String orderItemNumber;
protected String orderItemMaterialNumber;
protected String orderItemRequestedQuantity;
protected String orderItemStatus;
//getter-setter
}
Since you only need to log the values and not use them Overwrite the Object#toString method in all the Classes that hold information you want.
With this approach you can effectively have the information of every Object in one line.
For example
public class SOFTest {
privat int age, weight, height;
private Header header;
//Constructor etc.
#Overwrite
public String toString() {
return "SOFTest(" + String.format("%s, %s, %s %s)", age, weight, height, header.toString()));
}
}
I need to invoke the getter to the object of the custom class in readData(), so pass method.invoke(resp). It would be like this :
private static void readData(Object resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] allMethods = resp.getClass().getDeclaredMethods();
for (Method m : allMethods) {
if ("get".equalsIgnoreCase(m.getName().substring(0, 3))) {
Class<?> type = m.getReturnType();
if (isWrapperType(type) || type.isPrimitive()) {
System.out.println(m.invoke(resp)) ;
}
else if(Collection.class.isAssignableFrom(type)) {
if(m.getGenericReturnType() instanceof ParameterizedType){
ParameterizedType paramType = (ParameterizedType) m.getGenericReturnType();
System.out.println("List is of type "+(Class<?>) paramType.getActualTypeArguments()[0]);
}
}
else{
//Solution : need to invoke the getter to get the object and it would work
readData(method.invoke(resp));
}
}
}
}
I have unit test class and a static main entry method.
I know this is how I run the test class from my main method:
public class SingleJUnitTestRunner {
public static void main(String... args) throws ClassNotFoundException
{
String[] classAndMethod = args[0].split("#");
Request request = Request.method(Class.forName(classAndMethod[0]), classAndMethod[1]);
Result result = new JUnitCore().run(request);
System.exit(result.wasSuccessful() ? 0 : 1);
}
}
Is there a way to call a test-calls ctor with params and then run the tests?
I took a look at the Junit source code and came up with this:
public static void main(String... args) throws ClassNotFoundException {
String[] classAndMethod = args[0].split("#");
Object[] parameters = new Object[] {"constructor parameter"};
Class<?> className = Class.forName(classAndMethod[0]);
String methodName = classAndMethod[1];
Request request = createRequest(parameters, className, methodName);
Result result = new JUnitCore().run(request);
System.exit(result.wasSuccessful() ? 0 : 1);
}
private static Request createRequest(Object[] parameters, Class<?> className, String methodName) {
Description method = Description.createTestDescription(className, methodName);
return new ConstructorParameterRequest(className, parameters).filterWith(method);
}
Custom Request class so that we can use our own runner:
public class ConstructorParameterRequest extends Request {
private Class<?> clazz;
private Object[] parameters;
public ConstructorParameterRequest(Class<?> clazz, Object[] parameters) {
this.clazz = clazz;
this.parameters = parameters;
}
#Override
public Runner getRunner() {
try {
return new ConstructorParameterRunner(clazz, parameters);
} catch (Throwable e) {
return new ErrorReportingRunner(clazz, e);
}
}
}
Custom Runner class which creates the test class with constructor parameters. validateConstructor has to be overriden because BlockJUnit4ClassRunner checks for a zero argument constructor:
public class ConstructorParameterRunner extends BlockJUnit4ClassRunner {
private Object[] parameters;
public ConstructorParameterRunner(Class<?> clazz, Object[] parameters) throws InitializationError {
super(clazz);
this.parameters = parameters;
}
#Override
protected void validateConstructor(List<Throwable> errors) {
validateOnlyOneConstructor(errors);
}
#Override
protected Object createTest() throws Exception {
return getTestClass().getOnlyConstructor().newInstance(parameters);
}
}
I've faced with a requirement to deserialize fields that possibly can be transient using XStream 1.4.2. Despite of that, such fields may be annotated with both #XStreamAlias and #XStreamAsAttribute. Yes, I know, it sounds weird, and this is an indicator of bad design, but this is what I currently have. Since XStream offers a way to specify custom converter, I tried to extend com.thoughtworks.xstream.converters.reflection.ReflectionConverter in order to override the default way of omitting all transient fields trying to make XStream allow to deserialize them. However, I've fully stuck having two ideas to implement such a converter, but none of them works. So here is what I tried:
The 1st way doesn't work:
public final class TransientSimpleConverter extends ReflectionConverter {
private final Class<?> type;
private TransientSimpleConverter(Class<?> type, Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
this.type = type;
}
public static TransientSimpleConverter transientSimpleConverter(Class<?> type, XStream xStream) {
return new TransientSimpleConverter(type, xStream.getMapper(), xStream.getReflectionProvider());
}
#Override
protected boolean shouldUnmarshalTransientFields() {
return true;
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
}
The 2nd way doesn't work either:
public final class TransientComplexConverter extends ReflectionConverter {
private final Class<?> type;
private TransientComplexConverter(Class<?> type, Mapper mapper, ReflectionProvider provider) {
super(mapper, provider);
this.type = type;
}
public static TransientComplexConverter transientComplexConverter(Class<?> type, Mapper mapper, Iterable<String> fieldNames) {
return new TransientComplexConverter(type, mapper, TransientHackReflectionProvider.transientHackReflectionProvider(type, fieldNames));
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
private static final class TransientHackReflectionProvider extends PureJavaReflectionProvider {
private final Class<?> type;
private final Collection<Field> allowedFields;
private final Collection<String> allowedAliases;
private TransientHackReflectionProvider(Class<?> type, Collection<Field> allowedFields, Collection<String> allowedAliases) {
this.type = type;
this.allowedFields = allowedFields;
this.allowedAliases = allowedAliases;
}
public static TransientHackReflectionProvider transientHackReflectionProvider(final Class<?> type, Iterable<String> fieldNames) {
final Collection<Field> allowedFields = from(fieldNames).transform(new Function<String, Field>() {
#Override
public Field apply(String name) {
return field(type, name);
}
}).toList();
final Collection<String> allowedAliases = transform(allowedFields, new Function<Field, String>() {
#Override
public String apply(Field f) {
return f.getName();
}
});
return new TransientHackReflectionProvider(type, allowedFields, allowedAliases);
}
#Override
protected boolean fieldModifiersSupported(Field field) {
return allowedFields.contains(field) ? true : super.fieldModifiersSupported(field);
}
#Override
public boolean fieldDefinedInClass(String fieldName, Class type) {
return type == this.type && allowedAliases.contains(fieldName) ? true : super.fieldDefinedInClass(fieldName, type);
}
private static final Field field(Class<?> type, String name) {
try {
final Field field = type.getDeclaredField(name);
checkArgument(isTransient(field.getModifiers()), name + " is not transient");
checkArgument(field.getAnnotation(XStreamAsAttribute.class) != null, name + " must be annotated with XStreamAsAttribute");
checkArgument(field.getAnnotation(XStreamAlias.class) != null, name + " must be annotated with XStreamAlias");
return field;
} catch (final SecurityException ex) {
throw new RuntimeException(ex);
} catch (final NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
}
}
Any suggestions or ideas for a workaround? Thanks in advance.
I know this post is old, but maybe someone is still interested. My solution:
XStream xstream = new XStream(new MyPureJavaReflectionProvider());
class MyPureJavaReflectionProvider extends PureJavaReflectionProvider {
public MyPureJavaReflectionProvider() {
this(new FieldDictionary(new ImmutableFieldKeySorter()));
}
public MyPureJavaReflectionProvider(FieldDictionary fieldDictionary) {
super(fieldDictionary);
}
protected boolean fieldModifiersSupported(Field field) {
int modifiers = field.getModifiers();
return !Modifier.isStatic(modifiers);
}
public boolean fieldDefinedInClass(String fieldName, Class type) {
Field field = fieldDictionary.fieldOrNull(type, fieldName, null);
return field != null && fieldModifiersSupported(field);
}
}
I try to write own junit runner and currently I am stuck at returning proper test description.
public class ParameterizedWrapper extends Suite {
private List<Runner> fRunners;
/**
* #throws Throwable
*
*/
public ParameterizedWrapper(final Class<?> clazz) throws Throwable {
super(clazz, Collections.<Runner>emptyList());
fRunners = constructRunners(getParametersMethod());
}
protected List<Runner> constructRunners(final FrameworkMethod method) throws Exception, Throwable {
#SuppressWarnings("unchecked")
Iterable<Object[]> parameters = (Iterable<Object[]>) getParametersMethod().invokeExplosively(null);
ArrayList<Runner> runners = new ArrayList<Runner>();
int index = 0;
for (Object[] parameter : parameters) {
Class<?> testClass = getTestClass().getJavaClass();
WrappedRunner wrappedRunner = testClass.getAnnotation(WrappedRunner.class);
Runner runner = wrappedRunner.value().getConstructor(Class.class).newInstance(getTestClass().getJavaClass());
runners.add(new WrappingRunner(runner, parameter, testClass, index++));
}
return runners;
}
private FrameworkMethod getParametersMethod() throws Exception {
List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(Parameters.class);
for (FrameworkMethod each : methods) {
if (each.isStatic() && each.isPublic()) {
return each;
}
}
throw new Exception("No public static parameters method on class " + getTestClass().getName());
}
#Override
protected List<Runner> getChildren() {
return fRunners;
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public static #interface WrappedRunner {
Class<? extends Runner> value();
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
public static #interface ParameterSetter {
}
}
class WrappingRunner extends Runner {
private Runner wrappedRunner;
private Object[] parameters;
private Class<?> testClass;
private int testPosition;
public WrappingRunner(final Runner runner, final Object[] params, final Class<?> clazz, final int position) {
wrappedRunner = runner;
parameters = params;
testClass = clazz;
testPosition = position;
}
#Override
public Description getDescription() {
Description originalDescription = wrappedRunner.getDescription();
Description newDescription = Description.createSuiteDescription(nameFor(""), new Annotation[0]);
for (Description child : originalDescription.getChildren()) {
newDescription.addChild(decorateChildDescription(child));
}
return newDescription;
}
private String nameFor(String name) {
return String.format("%1$s[%2$s]", name, testPosition);
}
protected Description decorateChildDescription(final Description originalChildDescription) {
Description d = Description.createTestDescription(originalChildDescription.getTestClass(),
nameFor(originalChildDescription.getMethodName()),
originalChildDescription.getAnnotations().toArray(new Annotation[0]));
return d;
}
#Override
public void run(final RunNotifier notifier) {
try {
ParameterStorage.storeParameters(testClass, parameters);
wrappedRunner.run(notifier);
} finally {
ParameterStorage.clearParameters(testClass);
}
}
}
I have some test class to check if runner works. Runner works fine except tests are named weirdly. In eclipse it displays all tests and adds unrooted tests category
and surefire does not use my naming at all:
I compared description objects generated in my runner and in Parameterized runner, there seems to be no difference.
It's a bit ugly, but it's safer to pass the list of child runners to the parent constructor:
public ParameterizedWrapper(final Class<?> clazz) throws Throwable {
super(clazz, constructRunners(getParametersMethod());
}
private static List<Runner> constructRunners(final FrameworkMethod method)
throws Throwable {
...
You shouldn't need to override Suite.getChildren()
I have checked a bit more and found that description generated by my runner is ok. But that does not matter as it is inconsistent with description used during actual test execution. That is why eclipse shows entries as not executed and that is why surefire does not show my names.
Currently I think to use my own notifier to catch test start point and replace configuration at that point.
If someone has a better solution I would like to know about it :).
When you run a JUnit 4 ParameterizedTest with the Eclipse TestRunner, the graphical representation is rather dumb: for each test you have a node called [0], [1], etc.
Is it possible give the tests [0], [1], etc. explicit names? Implementing a toString method for the tests does not seem to help.
(This is a follow-up question to JUnit test with dynamic number of tests.)
I think there's nothing built in in jUnit 4 to do this.
I've implemented a solution. I've built my own Parameterized class based on the existing one:
public class MyParameterized extends TestClassRunner {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public static #interface Parameters {
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public static #interface Name {
}
public static Collection<Object[]> eachOne(Object... params) {
List<Object[]> results = new ArrayList<Object[]>();
for (Object param : params)
results.add(new Object[] { param });
return results;
}
// TODO: single-class this extension
private static class TestClassRunnerForParameters extends TestClassMethodsRunner {
private final Object[] fParameters;
private final Class<?> fTestClass;
private Object instance;
private final int fParameterSetNumber;
private final Constructor<?> fConstructor;
private TestClassRunnerForParameters(Class<?> klass, Object[] parameters, int i) throws Exception {
super(klass);
fTestClass = klass;
fParameters = parameters;
fParameterSetNumber = i;
fConstructor = getOnlyConstructor();
instance = fConstructor.newInstance(fParameters);
}
#Override
protected Object createTest() throws Exception {
return instance;
}
#Override
protected String getName() {
String name = null;
try {
Method m = getNameMethod();
if (m != null)
name = (String) m.invoke(instance);
} catch (Exception e) {
}
return String.format("[%s]", (name == null ? fParameterSetNumber : name));
}
#Override
protected String testName(final Method method) {
String name = null;
try {
Method m = getNameMethod();
if (m != null)
name = (String) m.invoke(instance);
} catch (Exception e) {
}
return String.format("%s[%s]", method.getName(), (name == null ? fParameterSetNumber : name));
}
private Constructor<?> getOnlyConstructor() {
Constructor<?>[] constructors = getTestClass().getConstructors();
assertEquals(1, constructors.length);
return constructors[0];
}
private Method getNameMethod() throws Exception {
for (Method each : fTestClass.getMethods()) {
if (Modifier.isPublic((each.getModifiers()))) {
Annotation[] annotations = each.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Name.class) {
if (each.getReturnType().equals(String.class))
return each;
else
throw new Exception("Name annotated method doesn't return an object of type String.");
}
}
}
}
return null;
}
}
// TODO: I think this now eagerly reads parameters, which was never the
// point.
public static class RunAllParameterMethods extends CompositeRunner {
private final Class<?> fKlass;
public RunAllParameterMethods(Class<?> klass) throws Exception {
super(klass.getName());
fKlass = klass;
int i = 0;
for (final Object each : getParametersList()) {
if (each instanceof Object[])
super.add(new TestClassRunnerForParameters(klass, (Object[]) each, i++));
else
throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fKlass.getName(), getParametersMethod().getName()));
}
}
private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
return (Collection<?>) getParametersMethod().invoke(null);
}
private Method getParametersMethod() throws Exception {
for (Method each : fKlass.getMethods()) {
if (Modifier.isStatic(each.getModifiers())) {
Annotation[] annotations = each.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Parameters.class)
return each;
}
}
}
throw new Exception("No public static parameters method on class " + getName());
}
}
public MyParameterized(final Class<?> klass) throws Exception {
super(klass, new RunAllParameterMethods(klass));
}
#Override
protected void validate(MethodValidator methodValidator) {
methodValidator.validateStaticMethods();
methodValidator.validateInstanceMethods();
}
}
To be used like:
#RunWith(MyParameterized.class)
public class ParameterizedTest {
private File file;
public ParameterizedTest(File file) {
this.file = file;
}
#Test
public void test1() throws Exception {}
#Test
public void test2() throws Exception {}
#Name
public String getName() {
return "coolFile:" + file.getName();
}
#Parameters
public static Collection<Object[]> data() {
// load the files as you want
Object[] fileArg1 = new Object[] { new File("path1") };
Object[] fileArg2 = new Object[] { new File("path2") };
Collection<Object[]> data = new ArrayList<Object[]>();
data.add(fileArg1);
data.add(fileArg2);
return data;
}
}
This implies that I instantiate the test class earlier. I hope this won't cause any errors ... I guess I should test the tests :)
JUnit4 now allows specifying a name attribute to the Parameterized annotation, such that you can specify a naming pattern from the index and toString methods of the arguments. E.g.:
#Parameters(name = "{index}: fib({0})={1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
A code-less though not that comfortable solution is to pass enough context information to identify the test in assert messages. You will still see just testXY[0] failed but the detailed message tells you which one was that.
assertEquals("Not the expected decision for the senator " + this.currentSenatorName + " and the law " + this.votedLaw,
expectedVote, actualVote);
If you use JUnitParams library (as I have described here), the parameterized tests will have their stringified parameters as their own default test names.
Moreover, you can see in their samples, that JUnitParams also allows you to have a custom test name by using #TestCaseName:
#Test
#Parameters({ "1,1", "2,2", "3,6" })
#TestCaseName("factorial({0}) = {1}")
public void custom_names_for_test_case(int argument, int result) { }
#Test
#Parameters({ "value1, value2", "value3, value4" })
#TestCaseName("[{index}] {method}: {params}")
public void predefined_macro_for_test_case_name(String param1, String param2) { }
There's no hint that this feature is or will be implemented. I would request this feature because it's nice to have.