Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an API for custom test method parameter injection #612

Open
Crazyjavahacking opened this issue Mar 3, 2015 · 13 comments
Open

Add an API for custom test method parameter injection #612

Crazyjavahacking opened this issue Mar 3, 2015 · 13 comments

Comments

@Crazyjavahacking
Copy link

I need a way to inject a custom parameter into TestNG's test methods. The parameter is my own object constructed dynamically at runtime from test method annotations. The custom parameter is not known to TestNG, it's my own type, e.g.:

@com.mypackage.MyAnnotation("someValue)"
@org.testng.Test
public void test(MyCustomObject val) {
    ...
}

It looks like right now I cannot do it easily.

Can we add such functionality/SPI? What is the current easiest way to achieve that?

@Crazyjavahacking
Copy link
Author

I don't want to use @dataProvider annotation.

@cbeust
Copy link
Collaborator

cbeust commented Mar 3, 2015

Why don't you want to use @dataProvider, it's the easiest approach by far
(just a few lines of code).

Cédric

On Tue, Mar 3, 2015 at 6:09 AM, Crazyjavahacking notifications@github.com
wrote:

I don't want to use @dataProvider annotation.

Reply to this email directly or view it on GitHub
#612 (comment).

@Crazyjavahacking
Copy link
Author

The problem is that the object I want to inject is dynamically created based on the data inside annotations on test method.

In my initial example, I need to parse the content of @MyAnnotation and based on that I need to create an instance of MyCustomObject.

@cbeust
Copy link
Collaborator

cbeust commented Mar 3, 2015

What is stopping you from doing all that in the data provider?

Cédric

On Tue, Mar 3, 2015 at 3:12 PM, Crazyjavahacking notifications@github.com
wrote:

The problem is that the object I want to inject is dynamically created
based on the data inside annotations on test method.

In my initial example, I need to parse the content of @MyAnnotation and
based on that I need to create an instance of MyCustomObject.

Reply to this email directly or view it on GitHub
#612 (comment).

@Crazyjavahacking
Copy link
Author

The functionality is not really parameterized testing and I also don't want to use data providers, e.g.:

@Columns("col1 | col2 || val)"
@Rows  ({"a    | b    || c",
         "d    | e    || f})
@org.testng.Test
public void test1(MyCustomObject val) {
    // MyCustomerObject is created from the annotation values
    // some menipulation with val ...
}

@Columns("col1 | col2 || val)"
@Rows  ({"1    | 2    || a",
         "3    | 3    || b})
@org.testng.Test
public void test1(MyCustomObject val) {
    // MyCustomerObject is created from the annotation values
    // some menipulation with val ...
}

so I don't think data providers are a good approach here

@g13013
Copy link

g13013 commented Nov 8, 2016

Would be very useful I have the same needs

@juherr
Copy link
Member

juherr commented Nov 9, 2016

The idea is interesting but as @cbeust said a long time ago, a data provider is a good workaround.
According to the previous sample:

@Columns("col1 | col2 || val)")
@Rows(  {"a    | b    || c",
         "d    | e    || f"})
@Test(dataProvider = "dp")
public void test1(MyCustomObject val) {
    // MyCustomerObject is created from the annotation values
    // some menipulation with val ...
}

@Columns("col1 | col2 || val)"
@Rows(  {"1    | 2    || a",
         "3    | 3    || b"})
@Test(dataProvider = "dp")
public void test1(MyCustomObject val) {
    // MyCustomerObject is created from the annotation values
    // some menipulation with val ...
}

@DataProvider
public static MyCustomObject[] dp(Method m) {
    Columns colums = m.getAnnotation(Columns.class);
    Rows rows = m.getAnnotation(Rows.class);
    MyCustomObject[] results = new MyCustomObject[rows.values().length];
    for (int i=0; i < rows.values().length; i++) {
        String value = rows.values()[i];
        // extract params with custom business
        results[i] = new MyCustomObject(/* params */);
    }
    return results;
}

@g13013
Copy link

g13013 commented Nov 9, 2016

this is true @juherr, i ended doing it this way. But, the only thing that misses is the abilitty to set a default dataProvider for all tests, in my case, my data provider is generics and passes only if there is certain annotations

i think that a combination of the dataProvider and adding the ability to set a default provider or provider class would do the trick and will open a hole new possibilities

@juherr
Copy link
Member

juherr commented Nov 10, 2016

@g13013 You mean something like

public class UseMyDataProvider implements IAnnotationTransformer {
  public void transform(ITest annotation, Class testClass, Constructor testConstructor, Method testMethod) {
    if (/* business logic (for example: testMethod has expected annotations) */) {
      annotation.setDataProviderClass(MyExternalClassWithDataProvider.class);
      annotation.setDataProvider("dataprovider name");
    }
  }
}

@gjuillot
Copy link
Contributor

I would also love to be able to use Guice to inject parameters in tests.
DataProvider + IAnnotationTransformer is an interesting workaround but it lack flexibility (for example it is impossible to set the IAnnotationTransformer via @listeners)

@juherr
Copy link
Member

juherr commented Feb 22, 2017

it is impossible to set the IAnnotationTransformer via @Listeners

@gjuillot It could be a feature request. Could you open an issue with that?

@gjuillot
Copy link
Contributor

I'll do. But still, the best ever for me would be to have the possibility to use Guice to inject parameters in tests.

@vlsi
Copy link
Contributor

vlsi commented Dec 22, 2022

Here's a proof of concept for injecting the parameters.

The limitation is it does not support @DataProvider. The issue with using Guice-injected and @DataProvider-injected values is it is not clear how to tell how the value should be injected.
I'm inclined that when test uses data provider, Guice-injected parameters should have extra annotation (e.g. @Inject)

diff --git a/testng-core/src/main/java/org/testng/internal/Parameters.java b/testng-core/src/main/java/org/testng/internal/Parameters.java
index 701de725..2856e0f1 100644
--- a/testng-core/src/main/java/org/testng/internal/Parameters.java
+++ b/testng-core/src/main/java/org/testng/internal/Parameters.java
@@ -6,6 +6,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Parameter;
 import java.util.*;
+import com.google.inject.Injector;
 import org.testng.DataProviderHolder;
 import org.testng.IDataProviderInterceptor;
 import org.testng.IDataProviderListener;
@@ -346,9 +347,9 @@ public class Parameters {
       return new Object[0];
     }
 
-    if (areAllOptionalValuesNull(optionalValues)) {
-      checkParameterTypes(method.getName(), parameterTypes, methodAnnotation, parameterNames);
-    }
+//    if (areAllOptionalValuesNull(optionalValues)) {
+//      checkParameterTypes(method.getName(), parameterTypes, methodAnnotation, parameterNames);
+//    }
     List<Object> vResult = Lists.newArrayList();
 
     List<Object> consParams =
@@ -388,7 +389,7 @@ public class Parameters {
   }
 
   private static boolean canInject(String annotation) {
-    return !("@" + Test.class.getSimpleName()).equalsIgnoreCase(annotation);
+    return true;
   }
 
   private static final List<Class<?>> INJECTED_TYPES =
diff --git a/testng-core/src/main/java/org/testng/internal/objects/GuiceBasedObjectDispenser.java b/testng-core/src/main/java/org/testng/internal/objects/GuiceBasedObjectDispenser.java
index 1f036222..b5d34145 100644
--- a/testng-core/src/main/java/org/testng/internal/objects/GuiceBasedObjectDispenser.java
+++ b/testng-core/src/main/java/org/testng/internal/objects/GuiceBasedObjectDispenser.java
@@ -49,6 +49,9 @@ class GuiceBasedObjectDispenser implements IObjectDispenser {
       if (injector == null) {
         return null;
       }
+      if (ctx != null) {
+        ctx.setAttribute(GUICE_INJECTOR, injector);
+      }
       if (sa.getRawClass() == null) {
         return injector.getInstance(sa.getTestClass().getRealClass());
       } else {
diff --git a/testng-core/src/main/java/org/testng/internal/objects/IObjectDispenser.java b/testng-core/src/main/java/org/testng/internal/objects/IObjectDispenser.java
index 94f66393..6b2b82d5 100644
--- a/testng-core/src/main/java/org/testng/internal/objects/IObjectDispenser.java
+++ b/testng-core/src/main/java/org/testng/internal/objects/IObjectDispenser.java
@@ -9,6 +9,7 @@ import org.testng.internal.objects.pojo.CreationAttributes;
 public interface IObjectDispenser {
 
   String GUICE_HELPER = "testng.guice-helper";
+  String GUICE_INJECTOR = "testng.guice-injector";
 
   Object dispense(CreationAttributes attributes);
 
diff --git a/testng-core/src/main/java/org/testng/internal/reflect/ReflectionRecipes.java b/testng-core/src/main/java/org/testng/internal/reflect/ReflectionRecipes.java
index 4b7eda5d..98468f3e 100644
--- a/testng-core/src/main/java/org/testng/internal/reflect/ReflectionRecipes.java
+++ b/testng-core/src/main/java/org/testng/internal/reflect/ReflectionRecipes.java
@@ -3,12 +3,16 @@ package org.testng.internal.reflect;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
 import java.util.*;
+import com.google.inject.Injector;
+import com.google.inject.Key;
 import org.testng.ITestContext;
 import org.testng.ITestResult;
 import org.testng.TestNGException;
 import org.testng.annotations.NoInjection;
 import org.testng.internal.RuntimeBehavior;
+import org.testng.internal.objects.IObjectDispenser;
 import org.testng.xml.XmlTest;
 
 /**
@@ -355,13 +359,14 @@ public final class ReflectionRecipes {
     if (filters == null || filters.isEmpty()) {
       return args;
     }
+    Injector injector = context == null ? null : (Injector) context.getAttribute(IObjectDispenser.GUICE_INJECTOR);
     final ArrayList<Object> arguments = new ArrayList<>(args.length);
     final ListBackedImmutableQueue<Object> queue = new ListBackedImmutableQueue<>(args);
     boolean firstMethodInjected = false;
     for (final Parameter parameter : parameters) {
       boolean inject = false;
-      Object injectObject = null;
       for (final InjectableParameter injectableParameter : filters) {
+        Object injectObject = null;
         inject = canInject(parameter, injectableParameter);
         if (inject) {
           switch (injectableParameter) {
@@ -391,6 +396,16 @@ public final class ReflectionRecipes {
           }
         }
       }
+      if (!inject && injector != null) {
+        try {
+          Type parameterizedType = parameter.getParameterizedType();
+          Object injectObject = injector.getInstance(Key.get(parameterizedType));
+          arguments.add(injectObject);
+          inject = true;
+        } catch (Exception e) {
+          // ignore
+        }
+      }
 
       if (!inject && !queue.backingList.isEmpty()) {
         arguments.add(queue.poll());
diff --git a/testng-core/src/test/java/test/guice/Guice1Test.java b/testng-core/src/test/java/test/guice/Guice1Test.java
index 75e5bc09..ec250f3c 100644
--- a/testng-core/src/test/java/test/guice/Guice1Test.java
+++ b/testng-core/src/test/java/test/guice/Guice1Test.java
@@ -11,6 +11,16 @@ public class Guice1Test extends SimpleBaseTest {
 
   @Inject ISingleton m_singleton;
 
+  @Test
+  public void injectParam(ISingleton singleton) {
+    System.out.println("singleto1n = " + singleton);
+  }
+
+  @Test
+  public void injectParam2(ISingleton singleton) {
+    System.out.println("singleton1 = " + singleton);
+  }
+
   @Test
   public void singletonShouldWork() {
     m_singleton.doSomething();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants