Skip to content

Commit

Permalink
Add option to share injectors between tests.
Browse files Browse the repository at this point in the history
Close #3
  • Loading branch information
roman.ivanitsky authored and JeffFaer committed Oct 18, 2018
1 parent 484f71d commit 2583e80
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static java.util.stream.Collectors.toSet;
import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations;
import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;

import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
Expand All @@ -25,6 +26,8 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;
import javax.inject.Qualifier;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand All @@ -39,6 +42,8 @@ public final class GuiceExtension implements TestInstancePostProcessor, Paramete
private static final Namespace NAMESPACE =
Namespace.create("name", "falgout", "jeffrey", "testing", "junit", "guice");

private static final ConcurrentMap<Set<? extends Class<?>>, Injector> INJECTOR_CACHE = new ConcurrentHashMap<>();

public GuiceExtension() {}

@Override
Expand All @@ -59,16 +64,22 @@ private static Optional<Injector> getOrCreateInjector(ExtensionContext context)
if (!context.getElement().isPresent()) {
return Optional.empty();
}

AnnotatedElement element = context.getElement().get();
Store store = context.getStore(NAMESPACE);

Injector injector = store.get(element, Injector.class);
boolean sharedInjector = isSharedInjector(context);
Set<Class<? extends Module>> moduleClasses = Collections.emptySet();
if (injector == null && sharedInjector) {
moduleClasses = getContextModuleTypes(context);
injector = INJECTOR_CACHE.get(moduleClasses);
}
if (injector == null) {
injector = createInjector(context);
store.put(element, injector);
if (sharedInjector && !moduleClasses.isEmpty()) {
INJECTOR_CACHE.put(moduleClasses, injector);
}
}

return Optional.of(injector);
}

Expand All @@ -79,12 +90,19 @@ private static Injector createInjector(ExtensionContext context)
InvocationTargetException {
Optional<Injector> parentInjector = getParentInjector(context);
List<? extends Module> modules = getNewModules(context);

return parentInjector
.map(injector -> injector.createChildInjector(modules))
.orElseGet(() -> Guice.createInjector(modules));
}

private static boolean isSharedInjector(ExtensionContext context) {
if (!context.getElement().isPresent()) {
return false;
}
AnnotatedElement element = context.getElement().get();
return isAnnotated(element, SharedInjectors.class);
}

private static Optional<Injector> getParentInjector(ExtensionContext context)
throws NoSuchMethodException,
InstantiationException,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package name.falgout.jeffrey.testing.junit.guice;

import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@Inherited
@ExtendWith(GuiceExtension.class)
public @interface SharedInjectors {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package name.falgout.jeffrey.testing.junit.guice;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Named;
import name.falgout.jeffrey.testing.junit.guice.SharedInjectorsTest.OuterClassModule;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(GuiceExtension.class)
@IncludeModule(OuterClassModule.class)
@SharedInjectors
class SharedInjectorsTest {

@Test
@IncludeModule(TestCaseModule.class)
@SharedInjectors
void test1(@Named("base") String base, @Named("test") String test) {
assertEquals("base", base);
assertEquals("test", test);
}

@Test
@SharedInjectors
@IncludeModule(OuterSharedClassModule.class)
void testCase2(int value) {
assertEquals(1, value);
}


@IncludeModule(InnerClassModule.class)
@Nested
class InnerClass {

@SharedInjectors
@IncludeModule(TestCaseModule.class)
@Test
void test1(@Named("base") String base,
@Named("inner") String inner,
@Named("test") String test) {
assertEquals("base", base);
assertEquals("test", test);
assertEquals("inner", inner);
}
}

@Nested
@IncludeModule(CachedModule.class)
@SharedInjectors
class FirstCachedInjectorTest {

@Test
void firstTest(long i) {
assertEquals(1, i);
}

@Test
void secondTest(long i) {
assertEquals(1, i);
}
}

@Nested
@IncludeModule(CachedModule.class)
@SharedInjectors
class SecondCachedInjectorTest {

@Test
void firstTest(long i) {
assertEquals(1, i);
}

@Test
void secondTest(long i) {
assertEquals(1, i);
}
}

@Nested
@IncludeModule(NonCachedModule.class)
class FirstNonCachedInjectorTest {

@Test
void test(long i) {
long expectedValue = NonCachedModule.SECOND_EXECUTED.get() ? 2 : 1;
assertEquals(expectedValue, i);
NonCachedModule.FIRST_EXECUTED.set(true);
}
}

@Nested
@IncludeModule(NonCachedModule.class)
class SecondNonCachedInjectorTest {

@Test
void test(long i) {
long expectedValue = NonCachedModule.FIRST_EXECUTED.get() ? 2 : 1;
assertEquals(expectedValue, i);
NonCachedModule.SECOND_EXECUTED.set(true);
}
}

@IncludeModule(OuterSharedClassModule.class)
@SharedInjectors
@Nested
class OuterSharedClass {

@IncludeModule(ExtraModule.class)
@Test
void testCase1(int value, @Named("extra") int extra) {
assertEquals(1, value);
assertEquals(ExtraModule.EXTRA2_EXECUTED.get() ? 2 : 1, extra);
ExtraModule.EXTRA1_EXECUTED.set(true);
}

@Test
@IncludeModule(ExtraModule.class)
void testCase2(int value, @Named("extra") int extra) {
assertEquals(1, value);
assertEquals(ExtraModule.EXTRA1_EXECUTED.get() ? 2 : 1, extra);
ExtraModule.EXTRA2_EXECUTED.set(true);
}
}

static class OuterSharedClassModule extends AbstractModule {

private final static AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);

@Override
protected void configure() {
int value = ATOMIC_INTEGER.incrementAndGet();
bind(int.class).toInstance(value);
assertEquals(1, value);
}
}

static class ExtraModule extends AbstractModule {

static final AtomicBoolean EXTRA1_EXECUTED = new AtomicBoolean(false);
static final AtomicBoolean EXTRA2_EXECUTED = new AtomicBoolean(false);
static final AtomicInteger INTEGER = new AtomicInteger(0);

@Override
protected void configure() {
bind(int.class).annotatedWith(Names.named("extra")).toInstance(INTEGER.incrementAndGet());
}
}

static class OuterClassModule extends AbstractModule {

@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("base")).toInstance("base");
}
}

static class InnerClassModule extends AbstractModule {

@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("inner")).toInstance("inner");
}
}

static class TestCaseModule extends AbstractModule {

@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("test")).toInstance("test");
}
}

static final class CachedModule extends AbstractModule {

static final AtomicLong ATOMIC_LONG = new AtomicLong(0);

@Override
protected void configure() {
bind(long.class).toInstance(ATOMIC_LONG.incrementAndGet());
}
}

static final class NonCachedModule extends AbstractModule {

static final AtomicLong ATOMIC_LONG = new AtomicLong(0);
//depend on which test executed first
static final AtomicBoolean FIRST_EXECUTED = new AtomicBoolean(false);
static final AtomicBoolean SECOND_EXECUTED = new AtomicBoolean(false);

@Override
protected void configure() {
bind(long.class).toInstance(ATOMIC_LONG.incrementAndGet());
}
}
}

0 comments on commit 2583e80

Please sign in to comment.