summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2014-10-04 12:52:32 -0400
committerTavian Barnes <tavianator@tavianator.com>2014-10-04 12:52:32 -0400
commit3fd5777a58f59218e3229c9ea11d816ecc6ae367 (patch)
tree53f937505806e010d2bc3134fa5507d0ff78d32c
parentb0bb30831a334ff30e3e6a13790bbc324db89de5 (diff)
parent8e24a47b69f940c31ac95451b4584b95de7c8670 (diff)
downloadsangria-3fd5777a58f59218e3229c9ea11d816ecc6ae367.tar.xz
Merge branch 'test'
-rw-r--r--pom.xml11
-rw-r--r--sangria-contextual/pom.xml6
-rw-r--r--sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java18
-rw-r--r--sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java21
-rw-r--r--sangria-lazy/pom.xml6
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java10
-rw-r--r--sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java21
-rw-r--r--sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java38
-rw-r--r--sangria-log4j/pom.xml6
-rw-r--r--sangria-log4j/src/main/java/com/tavianator/sangria/log4j/Log4jLoggerProvider.java5
-rw-r--r--sangria-log4j/src/test/java/com/tavianator/sangria/log4j/SangriaLog4jModuleTest.java6
-rw-r--r--sangria-slf4j/pom.xml6
-rw-r--r--sangria-slf4j/src/main/java/com/tavianator/sangria/slf4j/Slf4jLoggerProvider.java5
-rw-r--r--sangria-slf4j/src/test/java/com/tavianator/sangria/slf4j/SangriaSlf4jModuleTest.java6
-rw-r--r--sangria-test/pom.xml51
-rw-r--r--sangria-test/src/main/java/com/tavianator/sangria/test/AtomicMatcher.java38
-rw-r--r--sangria-test/src/main/java/com/tavianator/sangria/test/BestPracticesMatcher.java42
-rw-r--r--sangria-test/src/main/java/com/tavianator/sangria/test/SangriaMatchers.java31
-rw-r--r--sangria-test/src/main/java/com/tavianator/sangria/test/package-info.java25
-rw-r--r--sangria-test/src/test/java/com/tavianator/sangria/test/SangriaMatchersTest.java91
20 files changed, 410 insertions, 33 deletions
diff --git a/pom.xml b/pom.xml
index 3ae6035..90d166e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,12 @@
<dependency>
<groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-test</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.tavianator.sangria</groupId>
<artifactId>sangria-contextual</artifactId>
<version>${project.version}</version>
</dependency>
@@ -87,13 +93,13 @@
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
- <version>4.0-beta4</version>
+ <version>4.0-beta5</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
- <version>4.0-beta4</version>
+ <version>4.0-beta5</version>
</dependency>
<dependency>
@@ -290,6 +296,7 @@
<modules>
<module>sangria-core</module>
+ <module>sangria-test</module>
<module>sangria-contextual</module>
<module>sangria-slf4j</module>
<module>sangria-log4j</module>
diff --git a/sangria-contextual/pom.xml b/sangria-contextual/pom.xml
index c939076..702616f 100644
--- a/sangria-contextual/pom.xml
+++ b/sangria-contextual/pom.xml
@@ -38,6 +38,12 @@
</dependency>
<dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java
index 6d5e502..fdc1ad0 100644
--- a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java
+++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java
@@ -40,6 +40,7 @@ import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.ProvisionListener;
import com.tavianator.sangria.core.DelayedError;
+import com.tavianator.sangria.core.UniqueAnnotations;
/**
* A binder for {@link ContextSensitiveProvider}s.
@@ -139,10 +140,17 @@ public class ContextSensitiveBinder {
public void toContextSensitiveProvider(Key<? extends ContextSensitiveProvider<? extends T>> key) {
error.cancel();
- binder.bind(bindingKey).toProvider(new ProviderKeyAdapter<>(key));
+ binder.bind(bindingKey).toProvider(new ProviderKeyAdapter<>(key, makeLinkedKey(key)));
binder.bindListener(new BindingMatcher(bindingKey), new Trigger(bindingKey));
}
+ private <U> Key<U> makeLinkedKey(Key<U> key) {
+ Key<U> linkedKey = Key.get(key.getTypeLiteral(), UniqueAnnotations.create());
+ binder.bind(linkedKey)
+ .to(key);
+ return linkedKey;
+ }
+
@Override
public void toContextSensitiveProvider(ContextSensitiveProvider<? extends T> provider) {
error.cancel();
@@ -190,15 +198,19 @@ public class ContextSensitiveBinder {
private static class ProviderKeyAdapter<T> extends ProviderAdapter<T> implements ContextSensitiveProviderKeyBinding<T> {
private final Key<? extends ContextSensitiveProvider<? extends T>> providerKey;
+ private final Key<? extends ContextSensitiveProvider<? extends T>> linkedKey;
private Provider<? extends ContextSensitiveProvider<? extends T>> provider;
- ProviderKeyAdapter(Key<? extends ContextSensitiveProvider<? extends T>> providerKey) {
+ ProviderKeyAdapter(
+ Key<? extends ContextSensitiveProvider<? extends T>> providerKey,
+ Key<? extends ContextSensitiveProvider<? extends T>> linkedKey) {
this.providerKey = providerKey;
+ this.linkedKey = linkedKey;
}
@Inject
void inject(Injector injector) {
- provider = injector.getProvider(providerKey);
+ provider = injector.getProvider(linkedKey);
}
@Override
diff --git a/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java b/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java
index 1e2ab49..68250cb 100644
--- a/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java
+++ b/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java
@@ -28,6 +28,7 @@ import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
+import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
@@ -40,6 +41,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import static com.tavianator.sangria.test.SangriaMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -54,6 +56,10 @@ public class ContextSensitiveBinderTest {
public @Rule ExpectedException thrown = ExpectedException.none();
private static class SelfProvider implements ContextSensitiveProvider<String> {
+ @Inject
+ SelfProvider() {
+ }
+
// For testing getInjectionPoints() in the SPI below
@SuppressWarnings("unused")
@Inject Injector injector;
@@ -191,6 +197,21 @@ public class ContextSensitiveBinderTest {
assertThat(hasSelf.selfProvider.get(), equalTo("<unknown>"));
}
+ @Test
+ public void testBestPractices() {
+ Module module = new AbstractModule() {
+ @Override
+ protected void configure() {
+ ContextSensitiveBinder.create(binder())
+ .bind(String.class)
+ .annotatedWith(Names.named("self"))
+ .toContextSensitiveProvider(SelfProvider.class);
+ }
+ };
+ assertThat(module, is(atomic()));
+ assertThat(module, followsBestPractices());
+ }
+
private static class RequiredContextProvider implements ContextSensitiveProvider<String> {
@Override
public String getInContext(InjectionPoint injectionPoint) {
diff --git a/sangria-lazy/pom.xml b/sangria-lazy/pom.xml
index 4840af3..e2c806e 100644
--- a/sangria-lazy/pom.xml
+++ b/sangria-lazy/pom.xml
@@ -38,6 +38,12 @@
</dependency>
<dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java
index ff4a02a..57cf763 100644
--- a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java
@@ -31,14 +31,4 @@ public class SangriaLazyModule extends AbstractModule {
protected void configure() {
bindScope(LazySingleton.class, LazyScopes.LAZY_SINGLETON);
}
-
- @Override
- public boolean equals(Object o) {
- return o instanceof SangriaLazyModule;
- }
-
- @Override
- public int hashCode() {
- return SangriaLazyModule.class.hashCode();
- }
}
diff --git a/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java
index 69fabce..2895c7d 100644
--- a/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java
+++ b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java
@@ -20,12 +20,14 @@ package com.tavianator.sangria.lazy;
import javax.inject.Inject;
import javax.inject.Provider;
+import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;
import org.junit.Test;
+import static com.tavianator.sangria.test.SangriaMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -62,11 +64,6 @@ public class LazySingletonTest {
test(Stage.PRODUCTION, new SangriaLazyModule());
}
- @Test
- public void testDuplicateModule() {
- test(Stage.PRODUCTION, new SangriaLazyModule(), new SangriaLazyModule());
- }
-
private void test(Stage stage, Module... modules) {
int before = Scoped.INSTANCES.get();
@@ -80,4 +77,18 @@ public class LazySingletonTest {
assertThat(provider.get(), sameInstance(instance));
assertThat(Scoped.INSTANCES.get(), equalTo(before + 1));
}
+
+ @Test
+ public void testBestPractices() {
+ Module module = new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new SangriaLazyModule());
+ bind(Scoped.class);
+ }
+ };
+
+ assertThat(module, is(atomic()));
+ assertThat(module, followsBestPractices());
+ }
}
diff --git a/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java
index ff63c03..8bea02d 100644
--- a/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java
+++ b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java
@@ -35,6 +35,7 @@ import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import org.junit.Test;
+import static com.tavianator.sangria.test.SangriaMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -69,7 +70,12 @@ public class LazyTest {
}
private static class HasConcrete {
- @Inject Lazy<Concrete> lazy;
+ final Lazy<Concrete> lazy;
+
+ @Inject
+ HasConcrete(Lazy<Concrete> lazy) {
+ this.lazy = lazy;
+ }
}
private static class HasQualifiedAbstract {
@@ -88,6 +94,8 @@ public class LazyTest {
protected void configure() {
binder().requireExplicitBindings();
+ bind(HasConcrete.class);
+
bind(Concrete.class);
LazyBinder.create(binder())
.bind(Concrete.class);
@@ -95,11 +103,26 @@ public class LazyTest {
}));
}
+ @Test
+ public void testBestPractices() {
+ Module module = new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(HasConcrete.class);
+
+ bind(Concrete.class);
+ LazyBinder.create(binder())
+ .bind(Concrete.class);
+ }
+ };
+ assertThat(module, is(atomic()));
+ assertThat(module, followsBestPractices());
+ }
+
private void testHasConcrete(Injector injector) {
int before = Concrete.INSTANCES.get();
- HasConcrete hasConcrete = new HasConcrete();
- injector.injectMembers(hasConcrete);
+ HasConcrete hasConcrete = injector.getInstance(HasConcrete.class);
assertThat(Concrete.INSTANCES.get(), equalTo(before));
Concrete instance = hasConcrete.lazy.get();
@@ -109,8 +132,7 @@ public class LazyTest {
assertThat(instance2, sameInstance(instance));
assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
- HasConcrete hasConcrete2 = new HasConcrete();
- injector.injectMembers(hasConcrete2);
+ HasConcrete hasConcrete2 = injector.getInstance(HasConcrete.class);
assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
Concrete instance3 = hasConcrete2.lazy.get();
@@ -150,8 +172,7 @@ public class LazyTest {
private void testQualifiedAbstract(Injector injector) {
int before = Concrete.INSTANCES.get();
- HasQualifiedAbstract hasQualifiedAbstract = new HasQualifiedAbstract();
- injector.injectMembers(hasQualifiedAbstract);
+ HasQualifiedAbstract hasQualifiedAbstract = injector.getInstance(HasQualifiedAbstract.class);
assertThat(Concrete.INSTANCES.get(), equalTo(before));
Abstract instance = hasQualifiedAbstract.lazy.get();
@@ -161,8 +182,7 @@ public class LazyTest {
assertThat(instance2, sameInstance(instance2));
assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
- HasQualifiedAbstract hasQualifiedAbstract2 = new HasQualifiedAbstract();
- injector.injectMembers(hasQualifiedAbstract2);
+ HasQualifiedAbstract hasQualifiedAbstract2 = injector.getInstance(HasQualifiedAbstract.class);
assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
Abstract instance3 = hasQualifiedAbstract2.lazy.get();
diff --git a/sangria-log4j/pom.xml b/sangria-log4j/pom.xml
index 224081d..06e892a 100644
--- a/sangria-log4j/pom.xml
+++ b/sangria-log4j/pom.xml
@@ -39,6 +39,12 @@
</dependency>
<dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/sangria-log4j/src/main/java/com/tavianator/sangria/log4j/Log4jLoggerProvider.java b/sangria-log4j/src/main/java/com/tavianator/sangria/log4j/Log4jLoggerProvider.java
index dae2323..34262d7 100644
--- a/sangria-log4j/src/main/java/com/tavianator/sangria/log4j/Log4jLoggerProvider.java
+++ b/sangria-log4j/src/main/java/com/tavianator/sangria/log4j/Log4jLoggerProvider.java
@@ -19,6 +19,7 @@ package com.tavianator.sangria.log4j;
import javax.inject.Singleton;
+import com.google.inject.Inject;
import com.google.inject.spi.InjectionPoint;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -34,6 +35,10 @@ import com.tavianator.sangria.contextual.ContextSensitiveProvider;
*/
@Singleton
class Log4jLoggerProvider implements ContextSensitiveProvider<Logger> {
+ @Inject
+ Log4jLoggerProvider() {
+ }
+
@Override
public Logger getInContext(InjectionPoint injectionPoint) {
return LogManager.getLogger(injectionPoint.getDeclaringType().getRawType());
diff --git a/sangria-log4j/src/test/java/com/tavianator/sangria/log4j/SangriaLog4jModuleTest.java b/sangria-log4j/src/test/java/com/tavianator/sangria/log4j/SangriaLog4jModuleTest.java
index d91cf90..a3ecd66 100644
--- a/sangria-log4j/src/test/java/com/tavianator/sangria/log4j/SangriaLog4jModuleTest.java
+++ b/sangria-log4j/src/test/java/com/tavianator/sangria/log4j/SangriaLog4jModuleTest.java
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
+import static com.tavianator.sangria.test.SangriaMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -74,7 +75,8 @@ public class SangriaLog4jModuleTest {
}
@Test
- public void testDeDuplication() {
- Guice.createInjector(new SangriaLog4jModule(), new SangriaLog4jModule());
+ public void testBestPractices() {
+ assertThat(new SangriaLog4jModule(), is(atomic()));
+ assertThat(new SangriaLog4jModule(), followsBestPractices());
}
}
diff --git a/sangria-slf4j/pom.xml b/sangria-slf4j/pom.xml
index c274a47..53cbfb7 100644
--- a/sangria-slf4j/pom.xml
+++ b/sangria-slf4j/pom.xml
@@ -39,6 +39,12 @@
</dependency>
<dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/sangria-slf4j/src/main/java/com/tavianator/sangria/slf4j/Slf4jLoggerProvider.java b/sangria-slf4j/src/main/java/com/tavianator/sangria/slf4j/Slf4jLoggerProvider.java
index 916f2b9..bcb7e8b 100644
--- a/sangria-slf4j/src/main/java/com/tavianator/sangria/slf4j/Slf4jLoggerProvider.java
+++ b/sangria-slf4j/src/main/java/com/tavianator/sangria/slf4j/Slf4jLoggerProvider.java
@@ -19,6 +19,7 @@ package com.tavianator.sangria.slf4j;
import javax.inject.Singleton;
+import com.google.inject.Inject;
import com.google.inject.spi.InjectionPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,6 +35,10 @@ import com.tavianator.sangria.contextual.ContextSensitiveProvider;
*/
@Singleton
class Slf4jLoggerProvider implements ContextSensitiveProvider<Logger> {
+ @Inject
+ Slf4jLoggerProvider() {
+ }
+
@Override
public Logger getInContext(InjectionPoint injectionPoint) {
return LoggerFactory.getLogger(injectionPoint.getDeclaringType().getRawType());
diff --git a/sangria-slf4j/src/test/java/com/tavianator/sangria/slf4j/SangriaSlf4jModuleTest.java b/sangria-slf4j/src/test/java/com/tavianator/sangria/slf4j/SangriaSlf4jModuleTest.java
index d142bde..aaf72b2 100644
--- a/sangria-slf4j/src/test/java/com/tavianator/sangria/slf4j/SangriaSlf4jModuleTest.java
+++ b/sangria-slf4j/src/test/java/com/tavianator/sangria/slf4j/SangriaSlf4jModuleTest.java
@@ -28,6 +28,7 @@ import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
+import static com.tavianator.sangria.test.SangriaMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -75,7 +76,8 @@ public class SangriaSlf4jModuleTest {
}
@Test
- public void testDeDuplication() {
- Guice.createInjector(new SangriaSlf4jModule(), new SangriaSlf4jModule());
+ public void testBestPractices() {
+ assertThat(new SangriaSlf4jModule(), is(atomic()));
+ assertThat(new SangriaSlf4jModule(), followsBestPractices());
}
}
diff --git a/sangria-test/pom.xml b/sangria-test/pom.xml
new file mode 100644
index 0000000..cdb835d
--- /dev/null
+++ b/sangria-test/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria</artifactId>
+ <version>1.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sangria-test</artifactId>
+ <packaging>jar</packaging>
+ <name>Sangria Test</name>
+ <description>Utilities for testing with Guice</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-integration</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/sangria-test/src/main/java/com/tavianator/sangria/test/AtomicMatcher.java b/sangria-test/src/main/java/com/tavianator/sangria/test/AtomicMatcher.java
new file mode 100644
index 0000000..03c897e
--- /dev/null
+++ b/sangria-test/src/main/java/com/tavianator/sangria/test/AtomicMatcher.java
@@ -0,0 +1,38 @@
+package com.tavianator.sangria.test;
+
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Module;
+import com.google.inject.spi.Elements;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+
+/**
+ * Matcher that checks whether a {@link Module} can be installed multiple times.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+final class AtomicMatcher extends TypeSafeDiagnosingMatcher<Module> {
+ @Override
+ protected boolean matchesSafely(Module item, Description mismatchDescription) {
+ // Pass through the SPI to make sure the Module is atomic regardless of its equals() implementation
+ // This ensures atomicity even through Modules.override(), for example
+ Module copy1 = Elements.getModule(Elements.getElements(item));
+ Module copy2 = Elements.getModule(Elements.getElements(item));
+
+ try {
+ Guice.createInjector(copy1, copy2);
+ return true;
+ } catch (CreationException e) {
+ mismatchDescription.appendValue(e);
+ return false;
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("an atomic Module");
+ }
+}
diff --git a/sangria-test/src/main/java/com/tavianator/sangria/test/BestPracticesMatcher.java b/sangria-test/src/main/java/com/tavianator/sangria/test/BestPracticesMatcher.java
new file mode 100644
index 0000000..36eea97
--- /dev/null
+++ b/sangria-test/src/main/java/com/tavianator/sangria/test/BestPracticesMatcher.java
@@ -0,0 +1,42 @@
+package com.tavianator.sangria.test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Module;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+
+/**
+ * Matcher that checks whether a {@link Module} follows Guice best practices.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+final class BestPracticesMatcher extends TypeSafeDiagnosingMatcher<Module> {
+ private static final class EnforcerModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ binder().requireAtInjectOnConstructors();
+ binder().requireExactBindingAnnotations();
+ binder().requireExplicitBindings();
+ }
+ }
+
+ @Override
+ protected boolean matchesSafely(Module item, Description mismatchDescription) {
+ try {
+ Guice.createInjector(item, new EnforcerModule());
+ return true;
+ } catch (CreationException e) {
+ mismatchDescription.appendValue(e);
+ return false;
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("a Module following Guice best practices");
+ }
+}
diff --git a/sangria-test/src/main/java/com/tavianator/sangria/test/SangriaMatchers.java b/sangria-test/src/main/java/com/tavianator/sangria/test/SangriaMatchers.java
new file mode 100644
index 0000000..da8dd74
--- /dev/null
+++ b/sangria-test/src/main/java/com/tavianator/sangria/test/SangriaMatchers.java
@@ -0,0 +1,31 @@
+package com.tavianator.sangria.test;
+
+import com.google.inject.Module;
+import org.hamcrest.Matcher;
+
+/**
+ * Guice-related Hamcrest matchers.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public final class SangriaMatchers {
+ private SangriaMatchers() {
+ // Not for instantiating
+ }
+
+ /**
+ * @return A {@link Matcher} that checks whether a {@link Module}'s bindings can be de-duplicated successfully.
+ */
+ public static Matcher<Module> atomic() {
+ return new AtomicMatcher();
+ }
+
+ /**
+ * @return A {@link Matcher} that checks whether a {@link Module} follows Guice best practices.
+ */
+ public static Matcher<Module> followsBestPractices() {
+ return new BestPracticesMatcher();
+ }
+}
diff --git a/sangria-test/src/main/java/com/tavianator/sangria/test/package-info.java b/sangria-test/src/main/java/com/tavianator/sangria/test/package-info.java
new file mode 100644
index 0000000..0375640
--- /dev/null
+++ b/sangria-test/src/main/java/com/tavianator/sangria/test/package-info.java
@@ -0,0 +1,25 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+/**
+ * {@code sangria-test}: Utilities for testing with Guice.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+package com.tavianator.sangria.test;
diff --git a/sangria-test/src/test/java/com/tavianator/sangria/test/SangriaMatchersTest.java b/sangria-test/src/test/java/com/tavianator/sangria/test/SangriaMatchersTest.java
new file mode 100644
index 0000000..06fb45e
--- /dev/null
+++ b/sangria-test/src/test/java/com/tavianator/sangria/test/SangriaMatchersTest.java
@@ -0,0 +1,91 @@
+package com.tavianator.sangria.test;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import org.junit.Test;
+
+import static com.tavianator.sangria.test.SangriaMatchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link SangriaMatchers}.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public class SangriaMatchersTest {
+ private static class AtomicModule extends AbstractModule {
+ private static final Object INSTANCE = new Object();
+
+ @Override
+ protected void configure() {
+ bind(Object.class)
+ .toInstance(INSTANCE);
+ }
+ }
+
+ private static class NonAtomicModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(Object.class)
+ .toInstance(new Object());
+ }
+ }
+
+ @Test
+ public void testAtomic() {
+ assertThat(new AtomicModule(), is(atomic()));
+ assertThat(new NonAtomicModule(), is(not(atomic())));
+ }
+
+ private static class NoAtInjectModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(String.class);
+ }
+ }
+
+ private static class InexactBindingAnnotationModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(String.class)
+ .annotatedWith(Named.class)
+ .toInstance("test");
+ }
+
+ @Provides
+ String getString(@Named("test") String test) {
+ return test;
+ }
+ }
+
+ private static class Injectable {
+ @Inject
+ Injectable() {
+ }
+ }
+
+ private static class JustInTimeModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ }
+
+ @Provides
+ String getString(Injectable injectable) {
+ return "test";
+ }
+ }
+
+ @Test
+ public void testFollowsBestPractices() {
+ assertThat(new AtomicModule(), followsBestPractices());
+ assertThat(new NoAtInjectModule(), not(followsBestPractices()));
+ assertThat(new InexactBindingAnnotationModule(), not(followsBestPractices()));
+ assertThat(new JustInTimeModule(), not(followsBestPractices()));
+ }
+}