001    package org.apache.tapestry;
002    
003    import org.apache.tapestry.test.Creator;
004    import static org.easymock.EasyMock.createStrictControl;
005    import org.easymock.IMocksControl;
006    import org.testng.Assert;
007    import org.testng.annotations.AfterMethod;
008    
009    /**
010     * A base class for creating TestNG unit tests for Tapestry 4 applications. With slightly more
011     * effort, this may be used as a utility class with other frameworks, such as JUnit.
012     * <p>
013     * A single <em>strict</em> mock control is used for <strong>all</strong> mocks, which means that
014     * order of operations is checked not just for any single mock but across mocks.
015     * <p>
016     * Provides common mock factory and mock trainer methods.
017     * <p>
018     * Provides easy access to instantiated component instances.
019     * <p>
020     * Extends from {@link org.testng.Assert} to bring in all the public static assert methods without
021     * requiring extra imports.
022     * <p>
023     * TestNG supports running tests in parallel, as does this class. The EasyMock control is stored in
024     * a <em>thread local</em>. This is necessary as TestNG instantiates a single instance of the
025     * test case class, and invokes methods on it from multiple threads.
026     *
027     * @author Howard M. Lewis Ship
028     */
029    public class TestBase extends Assert
030    {
031        private static class ControlSource extends ThreadLocal<IMocksControl>
032        {
033            /** Creates a strick control for <em>this</em> thread. */
034            @Override
035            protected IMocksControl initialValue()
036            {
037                return createStrictControl();
038            }
039        }
040    
041        private final ControlSource _source = new ControlSource();
042    
043        // Access to this is synchronized.
044    
045        private Creator _creator;
046    
047        /**
048         * Creates a new instance of the provided class using the
049         * {@link org.apache.tapestry.test.Creator} utility.
050         *
051         * @param componentClass
052         *            the component type to build
053         * @param properties
054         *            alternating property names and property values to be injected into the instance
055         * @return the instantiated and configured component instance
056         */
057        public synchronized final <T> T newInstance(Class<T> componentClass, Object... properties)
058        {
059            if (_creator == null)
060                _creator = new Creator();
061    
062            Object instance = _creator.newInstance(componentClass, properties);
063    
064            return componentClass.cast(instance);
065        }
066    
067        /**
068         * Discards any mock objects created during the test. When using TestBase as a utility class,
069         * not a base class, you must be careful to either invoke this method, or discard the TestBase
070         * instance at the end of each test.
071         */
072        @AfterMethod(alwaysRun = true)
073        public final void cleanupControlSource()
074        {
075            // TestNG reuses the same class instance across all tests within that
076            // class, so if we don't
077            // clear out the mocks, they will tend to accumulate. That can get
078            // expensive, and can
079            // cause unexpected cascade errors when an earlier test fails.
080    
081            // After each method runs, we clear this thread's mocks control.
082            _source.remove();
083        }
084    
085        /**
086         * Creates a new mock object of the indicated type. The created object is retained for the
087         * duration of the test (specifically to support {@link #replay()} and {@link #verify()}).
088         *
089         * @param <T>
090         *            the type of the mock object
091         * @param mockClass
092         *            the class to mock
093         * @return the mock object, ready for training
094         */
095        public final <T> T newMock(Class<T> mockClass)
096        {
097            return getMocksControl().createMock(mockClass);
098        }
099    
100        /**
101         * Replay's the mocks control, preparing all mocks for testing.
102         */
103        public final void replay()
104        {
105            getMocksControl().replay();
106        }
107    
108        /**
109         * Verifies the mocks control, ensuring that all mocks completed all trained method invocations,
110         * then resets the control to allow more training of the mocks.
111         */
112        public final void verify()
113        {
114            IMocksControl control = getMocksControl();
115    
116            control.verify();
117            control.reset();
118        }
119    
120        /**
121         * Returns the control object used for all mocks created by this test case.
122         *
123         * @return The {@link IMocksControl} used to manage mocks.  
124         */
125        public final IMocksControl getMocksControl()
126        {
127            return _source.get();
128        }
129    
130        /**
131         * Sets the return value for the most recent method call upon the mock.
132         *
133         * @param returnValue
134         *            value to be returned from the method call
135         */
136        @SuppressWarnings("unchecked")
137        public final <T> void setReturnValue(T returnValue)
138        {
139            getMocksControl().andReturn(returnValue);
140        }
141    
142        /**
143         * Trains a mock object to throw an exception.
144         *
145         * @param throwable
146         *            the exception to be thrown by the most recent method call on the mock
147         */
148        public final void setThrowable(Throwable throwable)
149        {
150            getMocksControl().andThrow(throwable);
151        }
152    
153        /**
154         * Invoked to indicate code should not reach a point. This is typically used after code that
155         * should throw an exception.
156         */
157        public final void unreachable()
158        {
159            fail("This code should not be reachable.");
160        }
161    
162        /**
163         * Simply returns a new mock around {@link IRequestCycle}.
164         *
165         * @return The mocked request.
166         */
167        public final IRequestCycle newRequestCycle()
168        {
169            return newMock(IRequestCycle.class);
170        }
171    
172        /**
173         * Creates a mock for {@link IMarkupWriter}.
174         *
175         * @return The mock.
176         */
177        public final IMarkupWriter newMarkupWriter()
178        {
179            return newMock(IMarkupWriter.class);
180        }
181    }