1 package com.zmicer.utils.junit;
2
3 import com.zmicer.utils.InputArgumentUtils;
4 import com.zmicer.utils.LoggingUtils;
5 import com.zmicer.utils.ReflexionUtils;
6 import junit.framework.TestCase;
7 import org.apache.log4j.Logger;
8
9 import java.io.PrintWriter;
10 import java.io.StringWriter;
11 import java.lang.reflect.Method;
12 import java.util.regex.Matcher;
13
14 /**
15 * This class contains the functionality related to the JUnit itself and <strong>should</strong> :) be used for the increasing your
16 * productivity with junit.
17 * <br/>
18 * It contains also the tests for these method. This class is test case itself so it is not a problem to store the tests here to.
19 * note [zmicer]: if it is becoming large one - move the tests to the separated test class
20 * <p/>
21 * $Author:: $<br/>
22 * $Rev:: $<br/>
23 * $Date:: $<br/>
24 */
25 public class JUnitUtils extends TestCase
26 {
27 /**
28 * Logger instance.
29 */
30 final public static Logger LOG = Logger.getLogger(JUnitUtils.class);
31
32 /**
33 * Get the number of the tests methods at the provided class instance.
34 * todo [zmicer]: consider the necessarity of introducing the JUnitUtils class
35 *
36 * @param claz the class we need analyze. Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
37 *
38 * @return the number of the test methods. In the case of this class has not method - zero (<code>0</code>) would be returned
39 */
40 public static int getTestMethodsNumber(final Class claz)
41 {
42 InputArgumentUtils.checkObjects(claz);
43 final Method[] methods = claz.getMethods();
44 int result = 0;
45 for (final Method method : methods)
46 {
47 if (method.getName().startsWith("test"))
48 {
49 result++;
50 }
51 }
52 return result;
53 }
54
55 /**
56 * Test the given method on the wrong args. It tests all the cases and compositions. It could be used for the unit testing as allows us
57 * not to think on the IllegalArgumentException checks - this method check all this by itself, including all the possible combinations
58 * and cases.
59 * <br/>
60 * Be noticed that in the case of smth. wrong - e.g. invalid method or class was specified - the unit tests method <code>fail</code>
61 * is invoked with the description of what went wrong
62 * <br/>
63 * <strong>Example of working of this method:<strong>
64 * there is method with two first required arguments and two last not required. Then we would check the following combinations:<br/>
65 * <code>method(null, new smth, null, null)</code>
66 * <code>method(new smth, null, null, null)</code>
67 * <br/>
68 * Be noticed this method accesses only the public method of the class you are testing using JUnit.
69 *
70 * @param object the object on which we would check.
71 * Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
72 * @param methodName the method name we would test
73 * Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
74 * @param requiredInfo defines which of the parameters are required (for which the IllegalArgumentException should be thrown)
75 * Can not be null (otherwise <code>IllegalArgumentException</code> would appear). The number of the
76 * members of this array should be equal to the number of the <code>methodParamsClasses</code> arg var
77 * @param argsClasses the Array of classes to be used, Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
78 * @param args input arguments to be used on default (without null).
79 */
80 public static void checkOnWrongArgs(final Object object, final String methodName, final boolean[] requiredInfo, final Class[] argsClasses,
81 final Object... args)
82 {
83 InputArgumentUtils.checkObjects(object, methodName, requiredInfo, argsClasses, args);
84 if (requiredInfo.length != args.length)
85 {
86 throw new IllegalArgumentException("The numbers of boolean params in the req. info is not equal to the number of the method " +
87 "param classes");
88 }
89 try
90 {
91 final Class[] classes = new Class[args.length];
92 for (int i = 0; i < args.length; i++)
93 {
94 if (null == args[i])
95 {
96 throw new IllegalArgumentException("The default argument should not contains the null objects");
97 }
98 classes[i] = args[i].getClass();
99 }
100 final Method method = object.getClass().getDeclaredMethod(methodName, argsClasses);
101 assertNotNull(method);
102 for (int counter = 0; counter < args.length; counter++)
103 {
104 // only for the required argument.
105 if (requiredInfo[counter])
106 {
107 final Object[] objects = new Object[args.length];
108 int innerCounter = -1;
109 for (final Object innerParam : args)
110 {
111 innerCounter++;
112 if (innerCounter == counter || !requiredInfo[innerCounter])
113 {
114 objects[innerCounter] = null;
115 }
116 else
117 {
118 objects[innerCounter] = innerParam;
119 }
120 }
121 // failed sub-case: exception occuried: null input arguments
122 try
123 {
124 method.invoke(object, objects);
125 fail("Exception should have been occuried before this line.");
126 }
127 catch (Exception ex)
128 {
129 assertNotNull(ex.getCause());
130 if (!(ex.getCause() instanceof IllegalArgumentException))
131 {
132 LoggingUtils.logException(LOG, ex, "Incorrect Exception Occuried", object.getClass());
133 }
134 assertTrue(ex.getCause() instanceof IllegalArgumentException);
135 }
136 }
137 }
138 }
139 catch (Exception e)
140 {
141 LoggingUtils.logException(LOG, e, "Smth. went wrong", JUnitUtils.class);
142 fail("Smth. went wrong, Exception message [" + e.getMessage() + "]");
143 }
144 }
145
146 /**
147 * Run the test which should ended with the fail result.
148 *
149 * @param object object on which to run. Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
150 * @param methodName method to be runned. Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
151 * @param exceptionClaz exception to be thrown and then caught, Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
152 * @param argsClasses classes of the arguments (to find the appropriate method..)
153 * Can not be null (otherwise <code>IllegalArgumentException</code> would appear).
154 * @param arguments the var args defining the arguments to be passed to the running method
155 */
156 public static void runFailure(final Object object, final String methodName, final Class exceptionClaz, final Class[] argsClasses,
157 final Object... arguments)
158 {
159 InputArgumentUtils.checkObjects(object, methodName, exceptionClaz, argsClasses, arguments);
160 try
161 {
162 final Method method = object.getClass().getMethod(methodName, argsClasses);
163 assertNotNull(method);
164 try
165 {
166 method.invoke(object, arguments);
167 fail("Exception should have been occuried before this line.");
168 }
169 catch (Exception ex)
170 {
171 assertNotNull(ex.getCause());
172 if (!ReflexionUtils.instanceOf(ex.getCause(), exceptionClaz))
173 {
174 LoggingUtils.logException(LOG, ex, "Incorrect Exception Occuried", object.getClass());
175 }
176 assertTrue(ReflexionUtils.instanceOf(ex.getCause(), exceptionClaz));
177 }
178 }
179 catch (Exception e)
180 {
181 LoggingUtils.logException(LOG, e, "Smth. went wrong", JUnitUtils.class);
182 fail("Smth. went wrong, Exception message [" + e.getMessage() + "]");
183 }
184 }
185
186 /**
187 * todo [zmicer]: the current implementation of this functionality is not correct one. Still I consider it is possible to implement
188 * this. Review how it is done for the junit - it works. If you need this functionality somewhere - just adjust this sources
189 * note [zmicer]: only for the test purposes (for JPatterns test engine allowing to setup the props/xml environment per the folders)
190 * <p/>
191 * note [zmicer]: Does not use regular expressions. On default this method without parameter uses the second method from the trace of
192 * the methods, still if you you this not directly but via another utility method building pathes e.g. for the unit test configuration
193 * please then use method with parameters.
194 *
195 * @return the name of the running method.
196 */
197 public static String getRunningTestMethodName()
198 {
199 StringWriter sw = new StringWriter();
200 PrintWriter pw = new PrintWriter(sw);
201 new Exception().printStackTrace(pw);
202 // only for the debug
203 //System.out.println(sw.toString());
204
205 Matcher matcher = ReflexionUtils.PATTERN.matcher(sw.toString());
206 if (matcher.find() && matcher.groupCount() == 1)
207 {
208 final String res = matcher.group(1);
209 LoggingUtils.debug(ReflexionUtils.LOG, ReflexionUtils.class, "Currently running method name [" + res + "].");
210 return res;
211 }
212 LoggingUtils.debug(ReflexionUtils.LOG, ReflexionUtils.class, "Can not find the currently running method name. Smth. wrong in algorithm.");
213 return null;
214 }
215
216 /**
217 * @return the name of the method which is runned by the given unit test. This method should be used only for unit testing (only JUnit or
218 * maven unit tests plugin) and should be called inly at the test method itself (in simple words on the calls stack it should second after
219 * invoke0 method of the tests runner /zero/ and test method itself /first/)
220 */
221 public static String getRunningMethodName()
222 {
223 final String testMethodName = getRunningTestMethodName();
224 if (null != testMethodName && testMethodName.startsWith("test"))
225 {
226 String temp = testMethodName.substring("test".length());
227
228 return Character.toLowerCase(temp.charAt(0)) + temp.substring(1);
229 }
230
231 return null;
232 }
233 }