/* * Copyright (C) 2008 Google Inc. * * 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. */ package com.google.inject.testing.guiceberry.junit3; import com.google.common.annotations.VisibleForTesting; import com.google.common.testing.TearDown; import com.google.common.testing.TearDownAccepter; import com.google.guiceberry.DeprecatedGuiceBerryModule; import com.google.guiceberry.GuiceBerry; import com.google.guiceberry.GuiceBerry.GuiceBerryWrapper; import com.google.guiceberry.TestDescription; import com.google.inject.Guice; import com.google.inject.Provider; import com.google.inject.testing.guiceberry.GuiceBerryEnv; import com.google.inject.testing.guiceberry.NoOpTestScopeListener; import com.google.inject.testing.guiceberry.TestScopeListener; import junit.framework.TestCase; /** * Provides the tools to manage the JUnit tests that use {@code Guice}. * <p> * To use the {@link GuiceBerryJunit3} it is necessary to: * <ul><li> * Define a {@code Class<? extends Module>} that specifies the appropriate bindings. * <li> * Annotate all the JUnit tests that use those bindings with {@link GuiceBerryEnv} * annotation and set the value of this annotation to the name of the module. * <li> * Call the {@link GuiceBerryJunit3#setUp(TestCase)} within the * {@link TestCase#setUp()} method. * <li> * It is possible to define more than one module and different subclasses of * {@link TestCase} may use the different modules (but don't have to). * </ul> * <p> * GuiceBerry is thread-safe so tests can be run in parallel. * * @see Guice * * @author Luiz-Otavio Zorzella * @author Danka Karwanska */ public class GuiceBerryJunit3 { final GuiceBerry guiceBerry; @VisibleForTesting public GuiceBerryJunit3(GuiceBerry guiceBerry) { this.guiceBerry = guiceBerry; } private static TestDescription buildDescription(TestCase testCase) { return new TestDescription( testCase, testCase.getClass().getName() + "." + testCase.getName()); } private static final ThreadLocal<TearDown> scaffoldingThreadLocal = new ThreadLocal<TearDown>(); private static final GuiceBerryJunit3 INSTANCE = new GuiceBerryJunit3(GuiceBerry.INSTANCE); /** * Sets up the {@link TestCase} (given as the argument) to be ready to run. * <p> * The {@link TestCase} given as the argument needs to be annotated with the * {@link GuiceBerryEnv} annotation that provides the name of the * {@code Class<? extends Module>} which specifies all the injected values * used in this {@link TestCase}. It's necessary to add a binding that * binds {@link TestScopeListener} to an instance of the class that * implements this interface. If there is no such a {@link TestScopeListener} * it is possible to bind it to an instance of {@link NoOpTestScopeListener}. * It is also necessary to install the {@link BasicJunit3Module} * * <p> * Operations performed by this method include: * <ul> * <li> If the {@link TestCase} is the instance of * {@link com.google.common.testing.junit3.TearDownTestCase}, * the test case's {@code tearDown()} method is guaranteed to be executed * at the end of the test. * Otherwise calling {@link GuiceBerryJunit3#tearDown(TestCase)} is programmer's * responsibility. * <li> Gets the module from the class name provided by the * {@link GuiceBerryEnv} annotation and injects the bindings specified by * the module into the {@link TestCase}. It also binds {@link com.google.guiceberry.TestId} * and {@link TestCase} to the corresponding {@link Provider} in the scope * defined by {@link com.google.guiceberry.TestScoped} annotation. * <li> Notifies {@link TestScopeListener} that the test enters it's scope. * * * </ul> * <p> * It also starts storing the information that {@link TestCase} (given as the * argument) is being run. * * <p> * Note that you must call {@link #tearDown} if you call this method, unless * you're calling it from a subclass of {@link TearDownAccepter} in which case * it will be taken care of. * * * @param testCase The subclass of {@link TestCase} for which the * {@link GuiceBerryJunit3#setUp} is needed. * @throws IllegalArgumentException If the {@link TestCase} provided as * an argument has no {@link GuiceBerryEnv} annotation or the module * provided by this annotation does not exist or {@link TestCase} is not * a type of {@code Class <? extends Module>}. * @throws RuntimeException If the previous test has not been finished * properly or it cannot create the instance of * {@code Class <? extends Module>} (For example: module doesn't have no * argument constructor). Also if there is a problem with injecting all * the bindings or the {@link TestScopeListener} isn't binded to anything. * * @see TestScopeListener * @see com.google.guiceberry.TestId * @see com.google.guiceberry.TestScoped * @see GuiceBerryEnv */ public synchronized static void setUp(final TestCase testCase) { INSTANCE.doSetUp(testCase); } public synchronized void doSetUp(final TestCase testCase) { TestDescription testDescription = buildDescription(testCase); final GuiceBerryWrapper wrapper = guiceBerry.buildWrapper(testDescription, new VersionTwoBackwardsCompatibleGuiceBerryEnvSelector()); //add a tear down before setting up so that if an exception is thrown there, //we still do the tear down. maybeAddGuiceBerryTearDown(testDescription, new TearDown() { public void tearDown() throws Exception { wrapper.runAfterTest(); } }); wrapper.runBeforeTest(); } private static void maybeAddGuiceBerryTearDown( final TestDescription testDescription, final TearDown toTearDown) { DeprecatedGuiceBerryModule.maybeAddGuiceBerryTearDown( scaffoldingThreadLocal, testDescription, toTearDown); } /** * You should only call this method if your test does <em>not</em> implement * {@link TearDownAccepter}. * * <p>Stops storing the information that {@link TestCase} (given as the argument) * is being run. * * <p>Notifies {@link TestScopeListener} corresponding to this module ( * The {@link GuiceBerryEnv} annotation of the test case provides the name * of the module)that {@link TestCase} has just become out of scope. * It also causes that {@link TestCase} is removed from the {@code TestScope} * assigned to this module. * * <p>Do not change the value (if any) of the * {@link GuiceBerryEnvRemapper#GUICE_BERRY_ENV_REMAPPER_PROPERTY_NAME} * property, as it will likely cause the wrong {@link GuiceBerryEnv} to be * used on your tearDown. * * <p>If your code, for whatever reason, happens to synchronize on the test * class while calling the {@link #setUp(TestCase)} method, <em>and</em> you * run your tests in parallel, you should likewise synchronize the * {@link #tearDown(TestCase)}. * * @throws IllegalArgumentException If the {@link TestCase} provided as an * argument has no {@link GuiceBerryEnv} annotation or the module * provided by this annotation does not exist or {@link TestCase} is not * a type of {@code Class <? extends Module>}. * @throws RuntimeException If the method {@link GuiceBerryJunit3#setUp(TestCase)} * wasn't called before calling this method. * * @see TestScopeListener * @see com.google.guiceberry.TestScoped */ public synchronized static void tearDown(TestCase testCase) { if (testCase instanceof TearDownAccepter) { throw new UnsupportedOperationException("You must not call " + "GuiceBerryJunit3.tearDown (it's only needed for tests that do " + "not implement TearDownAccepter)."); } try { scaffoldingThreadLocal.get().tearDown(); } catch (Exception e) { throw new RuntimeException(e); } finally { scaffoldingThreadLocal.remove(); } } }