Repository: weld/weld-junit (java)

Issue:
WeldJunit5Extension + @Nested test strange behavior
```
@ExtendWith(WeldJunit5Extension.class)
class MyTest {
	@WeldSetup
	WeldInitiator weld = WeldInitiator.of(MyBean.class);
	@Inject
	MyBean myBean;
	
	@BeforeEach
	void setup() {
		myBean.mySetup();
	}
	
	@Nested
	class MyNestedTest {
		@WeldSetup
		WeldInitiator weld = WeldInitiator.of(MyBean.class);
		
		@Test
		void test() {
			assertNotNull(myBean);
		}
	}
}
```

With org.jboss.weld:weld-junit5:1.3.1.Final it's all green, the test passes.
Since org.jboss.weld:weld-junit5:2.0.0.Final it ends up with `NPE` in `setup()` instead.

I've created sample projects using weld-junit5 and spring-test for comparison. The spring analogue of test passes without any problems (below).

```
@SpringJUnitConfig(MyConfiguration.class)
class MyTest {
	@Inject
	MyBean myBean;
	
	@BeforeEach
	void setup() {
		myBean.mySetup();
	}
	
	@Nested
	@SpringJUnitConfig(MyConfiguration.class)
	class MyNestedTest {
		@Test
		void test() {
			assertNotNull(myBean);
		}
	}
}
```

[spring-test-nested-test.zip](https://github.com/weld/weld-junit/files/3739538/spring-test-nested-test.zip)
[weld-junit5-nested-test.zip](https://github.com/weld/weld-junit/files/3739539/weld-junit5-nested-test.zip)

Once again: I'm not sure if it is a regression or I should never use `WeldInitiator` in such way, but please, take a look.


Pull request description:
Swap to TestInstancePostProcessor to get hold of objects we want to t…
…urn into InjectionTarget. This fixes `@Nested` tests which inject into outer classes.

Fixes #87 

Patch:
diff --git a/junit5/src/main/java/org/jboss/weld/junit5/ExtensionContextUtils.java b/junit5/src/main/java/org/jboss/weld/junit5/ExtensionContextUtils.java
index 60f7a0f..bc9d337 100644
--- a/junit5/src/main/java/org/jboss/weld/junit5/ExtensionContextUtils.java
+++ b/junit5/src/main/java/org/jboss/weld/junit5/ExtensionContextUtils.java
@@ -16,7 +16,9 @@
  */
 package org.jboss.weld.junit5;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.jboss.weld.environment.se.WeldContainer;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -34,6 +36,7 @@ public class ExtensionContextUtils {
     private static final String CONTAINER = "weldContainer";
     private static final String EXPLICIT_PARAM_INJECTION = "explicitParamInjection";
     private static final String WELD_ENRICHERS = "weldEnrichers";
+    private static final String INSTANCES_TO_INJECT_TO = "instancesToInjectTo";
 
     private static Namespace EXTENSION_NAMESPACE;
 
@@ -80,6 +83,34 @@ public class ExtensionContextUtils {
         getTestStore(context).put(INITIATOR, initiator);
     }
 
+    /**
+     * For {@code @Nested} tests, this stores the enclosing class instance into root {@link ExtensionContext.Store}
+     * This has to be root store as each class and its nested classes have different {@link ExtensionContext}.
+     */
+    public static void setInstancesToInjectToToRootStore(ExtensionContext context, Object instanceToInjectTo) {
+        Set<Object> setOfObjects = getInstancesToInjectToFromRootStore(context);
+        if (setOfObjects == null) {
+            setOfObjects = new HashSet<>();
+        }
+        setOfObjects.add(instanceToInjectTo);
+        getRootExtensionStore(context).put(INSTANCES_TO_INJECT_TO, setOfObjects);
+    }
+
+    /**
+     * Root {@link ExtensionContext.Store} survives in between tests, we need to clear it after each test.
+     */
+    public static void clearInstancesToInjectToFromRootStore(ExtensionContext context) {
+        getRootExtensionStore(context).put(INSTANCES_TO_INJECT_TO, null);
+    }
+
+    /**
+     * For {@code @Nested} tests, this retrieves the enclosing class instances from root {@link ExtensionContext.Store}.
+     * This has to be root store as each class and its nested classes have different {@link ExtensionContext}.
+     */
+    public static Set<Object> getInstancesToInjectToFromRootStore(ExtensionContext context) {
+        return getRootExtensionStore(context).get(INSTANCES_TO_INJECT_TO, Set.class);
+    }
+
     /**
      * Return boolean indicating whether explicit parameter injection is enabled
      */
diff --git a/junit5/src/main/java/org/jboss/weld/junit5/WeldInitiator.java b/junit5/src/main/java/org/jboss/weld/junit5/WeldInitiator.java
index 728e73b..4eca1eb 100644
--- a/junit5/src/main/java/org/jboss/weld/junit5/WeldInitiator.java
+++ b/junit5/src/main/java/org/jboss/weld/junit5/WeldInitiator.java
@@ -17,6 +17,7 @@
 package org.jboss.weld.junit5;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -98,7 +99,7 @@ public class WeldInitiator extends AbstractWeldInitiator {
     /**
      * Create a builder instance.
      *
-     * @param weld
+     * @param beanClasses
      * @return a builder instance
      * @see #of(Class...)
      */
@@ -187,9 +188,13 @@ public class WeldInitiator extends AbstractWeldInitiator {
         if (weld == null) {
             weld = createWeld().addPackage(false, testInstance.getClass());
         }
-        // this ensures the test instance is always an InjectionTarget
-        instancesToInject.add(createToInject(testInstance));
 
         return initWeldContainer(weld);
     }
+
+    void addObjectsToInjectInto(Set<Object> instancesToInjectInto) {
+        for (Object o : instancesToInjectInto) {
+            instancesToInject.add(createToInject(o));
+        }
+    }
 }
diff --git a/junit5/src/main/java/org/jboss/weld/junit5/WeldJunit5Extension.java b/junit5/src/main/java/org/jboss/weld/junit5/WeldJunit5Extension.java
index 3335f9f..eab71c9 100644
--- a/junit5/src/main/java/org/jboss/weld/junit5/WeldJunit5Extension.java
+++ b/junit5/src/main/java/org/jboss/weld/junit5/WeldJunit5Extension.java
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.ParameterContext;
 import org.junit.jupiter.api.extension.ParameterResolutionException;
 import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.TestInstancePostProcessor;
 
 import javax.enterprise.inject.spi.BeanManager;
 import java.lang.annotation.Annotation;
@@ -38,13 +39,17 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.ServiceLoader;
+import java.util.Set;
 import java.util.stream.Collectors;
 
+import static org.jboss.weld.junit5.ExtensionContextUtils.clearInstancesToInjectToFromRootStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.getContainerFromStore;
+import static org.jboss.weld.junit5.ExtensionContextUtils.getInstancesToInjectToFromRootStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.getEnrichersFromStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.getExplicitInjectionInfoFromStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.getInitiatorFromStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.setContainerToStore;
+import static org.jboss.weld.junit5.ExtensionContextUtils.setInstancesToInjectToToRootStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.setEnrichersToStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.setExplicitInjectionInfoToStore;
 import static org.jboss.weld.junit5.ExtensionContextUtils.setInitiatorToStore;
@@ -82,7 +87,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;
  * @see WeldJunitEnricher
  */
 public class WeldJunit5Extension implements AfterAllCallback, BeforeAllCallback,
-        BeforeEachCallback, AfterEachCallback, ParameterResolver {
+        BeforeEachCallback, AfterEachCallback, ParameterResolver, TestInstancePostProcessor {
 
     // global system property
     public static final String GLOBAL_EXPLICIT_PARAM_INJECTION = "org.jboss.weld.junit5.explicitParamInjection";
@@ -109,6 +114,8 @@ public class WeldJunit5Extension implements AfterAllCallback, BeforeAllCallback,
         if (determineTestLifecycle(context).equals(PER_CLASS)) {
             getInitiatorFromStore(context).shutdownWeld();
         }
+        // root context survives in between test classes, we need to clear information stored there
+        clearInstancesToInjectToFromRootStore(context);
     }
 
     @Override
@@ -268,8 +275,22 @@ public class WeldJunit5Extension implements AfterAllCallback, BeforeAllCallback,
             }
             setInitiatorToStore(context, initiator);
 
+            // this ensures the test class is injected into
+            // in case of nested tests, this also injects into any outer classes
+            Set<Object> enclosingClasses = getInstancesToInjectToFromRootStore(context);
+            if (enclosingClasses != null) {
+                initiator.addObjectsToInjectInto(enclosingClasses);
+            }
+
             // and finally, init Weld
             setContainerToStore(context, initiator.initWeld(testInstance));
         }
     }
+
+    @Override
+    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
+        // in case of nested tests, this method gets invoked for the outer class and the inner class
+        // therefore we can use that to collect instances of objects we want to inject into
+        setInstancesToInjectToToRootStore(context, testInstance);
+    }
 }


Test patch:
diff --git a/junit5/src/test/java/org/jboss/weld/junit5/nested/MyBean.java b/junit5/src/test/java/org/jboss/weld/junit5/nested/MyBean.java
new file mode 100644
index 0000000..c0edd6e
--- /dev/null
+++ b/junit5/src/test/java/org/jboss/weld/junit5/nested/MyBean.java
@@ -0,0 +1,11 @@
+package org.jboss.weld.junit5.nested;
+
+import javax.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class MyBean {
+
+    public String ping() {
+        return MyBean.class.getSimpleName();
+    }
+}
diff --git a/junit5/src/test/java/org/jboss/weld/junit5/nested/NestedTestClassTest.java b/junit5/src/test/java/org/jboss/weld/junit5/nested/NestedTestClassTest.java
new file mode 100644
index 0000000..d40e9c8
--- /dev/null
+++ b/junit5/src/test/java/org/jboss/weld/junit5/nested/NestedTestClassTest.java
@@ -0,0 +1,57 @@
+package org.jboss.weld.junit5.nested;
+
+import org.jboss.weld.junit5.WeldInitiator;
+import org.jboss.weld.junit5.WeldJunit5Extension;
+import org.jboss.weld.junit5.WeldSetup;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import javax.inject.Inject;
+
+/**
+ * Tests a {@code @BeforeEach} method in superclass when actual test is in the inner class.
+ * {@code @BeforeEach} should still be invoked for nested classes. For the below scenario to work,
+ * we need to make the parent class an injection target as well.
+ */
+@ExtendWith(WeldJunit5Extension.class)
+public class NestedTestClassTest {
+
+    @WeldSetup
+    WeldInitiator weld = WeldInitiator.of(MyBean.class);
+
+    @Inject
+    MyBean myBean;
+
+    @BeforeEach
+    void setup() {
+        Assertions.assertNotNull(myBean);
+        myBean.ping();
+    }
+
+    @Nested
+    class MyNestedTest {
+        @WeldSetup
+        WeldInitiator weld = WeldInitiator.of(MyBean.class);
+
+        @Test
+        void testNestedClass() {
+            Assertions.assertNotNull(myBean);
+            myBean.ping();
+        }
+
+        @Nested
+        class TwiceNestedTest {
+            @WeldSetup
+            WeldInitiator weld = WeldInitiator.of(MyBean.class);
+
+            @Test
+            void testTwiceNestedClass() {
+                Assertions.assertNotNull(myBean);
+                myBean.ping();
+            }
+        }
+    }
+}