Last active
December 13, 2020 00:15
-
-
Save gaspardpetit/61e95523a6a7672dd65c97a913cce33f to your computer and use it in GitHub Desktop.
Google Guice TestScope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import com.google.inject.AbstractModule; | |
| import com.google.inject.Guice; | |
| import com.google.inject.Injector; | |
| import com.google.inject.Key; | |
| import com.google.inject.OutOfScopeException; | |
| import com.google.inject.Provider; | |
| import com.google.inject.Scope; | |
| import com.google.inject.ScopeAnnotation; | |
| import com.google.inject.Scopes; | |
| import org.junit.jupiter.api.AfterEach; | |
| import org.junit.jupiter.api.Assertions; | |
| import org.junit.jupiter.api.BeforeEach; | |
| import org.junit.jupiter.api.RepeatedTest; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.Target; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import static com.google.common.base.Preconditions.checkState; | |
| import static java.lang.annotation.ElementType.METHOD; | |
| import static java.lang.annotation.ElementType.TYPE; | |
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
| @Target({ TYPE, METHOD }) | |
| @Retention(RUNTIME) | |
| @ScopeAnnotation | |
| @interface TestScoped | |
| {} | |
| // from https://github.com/google/guice/wiki/CustomScopes | |
| class SimpleScope implements Scope | |
| { | |
| private static final Provider<Object> SEEDED_KEY_PROVIDER = | |
| () -> { | |
| throw new IllegalStateException("If you got here then it means that" + | |
| " your code asked for scoped object which should have been" + | |
| " explicitly seeded in this scope by calling" + | |
| " SimpleScope.seed(), but was not."); | |
| }; | |
| private Map<Key<?>, Object> values; | |
| public void enter() { | |
| checkState(values == null, "A scoping block is already in progress"); | |
| values = new HashMap<>(); | |
| } | |
| public void exit() { | |
| checkState(values != null, "No scoping block in progress"); | |
| values = null; | |
| } | |
| public <T> void seed(Key<T> key, T value) { | |
| Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key); | |
| checkState(!scopedObjects.containsKey(key), "A value for the key %s was " + | |
| "already seeded in this scope. Old value: %s New value: %s", key, | |
| scopedObjects.get(key), value); | |
| scopedObjects.put(key, value); | |
| } | |
| public <T> void seed(Class<T> clazz, T value) { | |
| seed(Key.get(clazz), value); | |
| } | |
| public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { | |
| return () -> { | |
| Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key); | |
| @SuppressWarnings("unchecked") | |
| T current = (T) scopedObjects.get(key); | |
| if (current == null && !scopedObjects.containsKey(key)) { | |
| current = unscoped.get(); | |
| // don't remember proxies; these exist only to serve circular dependencies | |
| if (Scopes.isCircularProxy(current)) { | |
| return current; | |
| } | |
| scopedObjects.put(key, current); | |
| } | |
| return current; | |
| }; | |
| } | |
| private <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) { | |
| Map<Key<?>, Object> scopedObjects = values; | |
| if (scopedObjects == null) { | |
| throw new OutOfScopeException("Cannot access " + key | |
| + " outside of a scoping block"); | |
| } | |
| return scopedObjects; | |
| } | |
| @SuppressWarnings({"unchecked"}) | |
| public static <T> Provider<T> seededKeyProvider() { | |
| return (Provider<T>) SEEDED_KEY_PROVIDER; | |
| } | |
| } | |
| class TestScope extends SimpleScope | |
| {} | |
| @TestScoped | |
| class ScopedObj | |
| {} | |
| class TestScopeTest | |
| { | |
| private final Injector m_injector = Guice.createInjector(new AbstractModule() { | |
| @Override protected void configure() { | |
| TestScope testScope = new TestScope(); | |
| bindScope(TestScoped.class, testScope); | |
| bind(TestScope.class).toInstance(testScope); | |
| } | |
| }); | |
| @BeforeEach | |
| void onBeginTest() { | |
| m_injector.getInstance(TestScope.class).enter(); | |
| } | |
| @AfterEach | |
| void onFinishTest() { | |
| m_injector.getInstance(TestScope.class).exit(); | |
| } | |
| static ScopedObj s_previousScopedObj; | |
| @RepeatedTest(2) | |
| void givenTest_whenGetTestScoped_ensureIsScopedToTest() { | |
| // when | |
| ScopedObj obj = m_injector.getInstance(ScopedObj.class); | |
| ScopedObj objAgain = m_injector.getInstance(ScopedObj.class); | |
| // ensure | |
| Assertions.assertSame(obj, objAgain); | |
| Assertions.assertNotSame(s_previousScopedObj, obj); | |
| s_previousScopedObj = objAgain; | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment