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