##### Coverage Report - org.jbehave.core.steps.Steps

Classes in this File | Line Coverage | Branch Coverage | Complexity
Steps |
92% | 117/126
94% | 55/58
| 2.1724137931034484;2.172
Steps$DuplicateCandidateFound |
100% | 2/2
N/A | N/A
| 2.1724137931034484;2.172

1 |  |
     package org.jbehave.core.steps;
2 |  |
3 |  |
     import java.lang.annotation.Annotation;
4 |  |
     import java.lang.reflect.Method;
5 |  |
     import java.util.ArrayList;
6 |  |
     import java.util.List;
7 |  |
8 |  |
     import org.apache.commons.lang.builder.ToStringBuilder;
9 |  |
     import org.apache.commons.lang.builder.ToStringStyle;
10 |  |
     import org.jbehave.core.annotations.AfterScenario;
11 |  |
     import org.jbehave.core.annotations.AfterScenario.Outcome;
12 |  |
     import org.jbehave.core.annotations.AfterStories;
13 |  |
     import org.jbehave.core.annotations.AfterStory;
14 |  |
     import org.jbehave.core.annotations.Alias;
15 |  |
     import org.jbehave.core.annotations.Aliases;
16 |  |
     import org.jbehave.core.annotations.BeforeScenario;
17 |  |
     import org.jbehave.core.annotations.BeforeStories;
18 |  |
     import org.jbehave.core.annotations.BeforeStory;
19 |  |
     import org.jbehave.core.annotations.Composite;
20 |  |
     import org.jbehave.core.annotations.Given;
21 |  |
     import org.jbehave.core.annotations.ScenarioType;
22 |  |
     import org.jbehave.core.annotations.Then;
23 |  |
     import org.jbehave.core.annotations.When;
24 |  |
     import org.jbehave.core.configuration.Configuration;
25 |  |
     import org.jbehave.core.configuration.MostUsefulConfiguration;
26 |  |
     import org.jbehave.core.parsers.RegexPrefixCapturingPatternParser;
27 |  |
     import org.jbehave.core.parsers.StepPatternParser;
28 |  |
     import org.jbehave.core.steps.StepCollector.Stage;
29 |  |
30 |  |
     import static java.util.Arrays.asList;
31 |  |
     import static org.jbehave.core.annotations.AfterScenario.Outcome.ANY;
32 |  |
     import static org.jbehave.core.annotations.AfterScenario.Outcome.FAILURE;
33 |  |
     import static org.jbehave.core.annotations.AfterScenario.Outcome.SUCCESS;
34 |  |
     import static org.jbehave.core.steps.StepType.GIVEN;
35 |  |
     import static org.jbehave.core.steps.StepType.THEN;
36 |  |
     import static org.jbehave.core.steps.StepType.WHEN;
37 |  |
38 |  |
     /**
39 |  |
      * <p>
40 |  |
      * Default implementation of {@link CandidateSteps} which provides the step
41 |  |
      * candidates that match the steps being run.
42 |  |
      * </p>
43 |  |
      * <p>
44 |  |
      * To provide your step candidate methods, you can:
45 |  |
      * <ul>
46 |  |
      * <li>pass in the steps instance type and the steps factory used to instantiate
47 |  |
      * the instance if any candidate steps are matched (lazy "has-a" relationship)</li>
48 |  |
      * <li>pass in the steps instance, instantiated regardless of whether the
49 |  |
      * candidate steps are matched (eager "has-a" relationship)</li>
50 |  |
      * <li>extend the {@link Steps} class, in which case the instance is the
51 |  |
      * extended {@link Steps} class itself ("is-a" relationship)</li>
52 |  |
      * </ul>
53 |  |
      * <b>The "has-a" design model, in which the steps instance is passed in, is
54 |  |
      * strongly recommended over the "is-a" model as it does not have tie-ins in the
55 |  |
      * {@link Steps} class implementation</b>.
56 |  |
      * </p>
57 |  |
      * <p>
58 |  |
      * You can define the methods that should be run by annotating them with
59 |  |
      * {@link Given @Given}, {@link When @When} or {@link Then @Then}, and providing
60 |  |
      * as a value for each annotation a pattern matches the textual step. The value
61 |  |
      * is interpreted by the {@link StepPatternParser}, which by default is a
62 |  |
      * {@link RegexPrefixCapturingPatternParser} that interprets the words starting
63 |  |
      * with '$' as parameters.
64 |  |
      * </p>
65 |  |
      * <p>
66 |  |
      * For instance, you could define a method as:
67 |  |
      *
68 |  |
      * <pre>
69 |  |
      * @When("I log in as $username with password: $password")
70 |  |
      * public void logIn(String username, String password) { //... }
71 |  |
      * </pre>
72 |  |
      *
73 |  |
      * and this would match the step:
74 |  |
      *
75 |  |
      * <pre>
76 |  |
      * When I log in as Liz with password: Pa55word
77 |  |
      * </pre>
78 |  |
      *
79 |  |
      * </p>
80 |  |
      * <p>
81 |  |
      * When the step is performed, the parameters matched will be passed to the
82 |  |
      * method, so in this case the effect will be to invoke:
83 |  |
      * </p>
84 |  |
      *
85 |  |
      * <pre>
86 |  |
      * logIn(&quot;Liz&quot;, &quot;Pa55word&quot;);
87 |  |
      * </pre>
88 |  |
      * <p>
89 |  |
      * The {@link Configuration} can be used to provide customize the
90 |  |
      * {@link StepCandidate}s that are created, e.g. providing a step monitor or
91 |  |
      * creating them in "dry run" mode.
92 |  |
      * </p>
93 |  |
      */
94 |  |
     public class Steps implements CandidateSteps {
95 |  |
96 |  |
         private final Configuration configuration;
97 |  |
         private Class<?> type;
98 |  |
         private InjectableStepsFactory stepsFactory;
99 |  |
100 |  |
         /**
101 |  |
          * Creates Steps with default configuration for a class extending this
102 |  |
          * instance and containing the candidate step methods
103 |  |
          */
104 |  |
         public Steps() {
105 | 105 |
             this(new MostUsefulConfiguration());
106 | 105 |
         }
107 |  |
108 |  |
         /**
109 |  |
          * Creates Steps with given custom configuration for a class extending this
110 |  |
          * instance and containing the candidate step methods
111 |  |
          *
112 |  |
          * @param configuration the Configuration
113 |  |
          */
114 | 112 |
         public Steps(Configuration configuration) {
115 | 112 |
             this.configuration = configuration;
116 | 112 |
             this.type = this.getClass();
117 | 112 |
             this.stepsFactory = new InstanceStepsFactory(configuration, this);
118 | 112 |
         }
119 |  |
120 |  |
         /**
121 |  |
          * Creates Steps with given custom configuration and a steps instance
122 |  |
          * containing the candidate step methods
123 |  |
          *
124 |  |
          * @param configuration the Configuration
125 |  |
          * @param instance the steps instance
126 |  |
          */
127 | 0 |
         public Steps(Configuration configuration, Object instance) {
128 | 0 |
             this.configuration = configuration;
129 | 0 |
             this.type = instance.getClass();
130 | 0 |
             this.stepsFactory = new InstanceStepsFactory(configuration, instance);
131 | 0 |
         }
132 |  |
133 |  |
         /**
134 |  |
          * Creates Steps with given custom configuration and a steps instance type
135 |  |
          * containing the candidate step methods. The steps instance is created
136 |  |
          * using the steps instance factory provided.
137 |  |
          *
138 |  |
          * @param configuration the Configuration
139 |  |
          * @param type the steps instance type
140 |  |
          * @param stepsFactory the {@link InjectableStepsFactory}
141 |  |
          */
142 | 17 |
         public Steps(Configuration configuration, Class<?> type, InjectableStepsFactory stepsFactory) {
143 | 17 |
             this.configuration = configuration;
144 | 17 |
             this.type = type;
145 | 17 |
             this.stepsFactory = stepsFactory;
146 | 17 |
         }
147 |  |
148 |  |
         public Class<?> type() {
149 | 0 |
             return type;
150 |  |
         }
151 |  |
152 |  |
         public Object instance() {
153 | 25 |
             return stepsFactory.createInstanceOfType(type);
154 |  |
         }
155 |  |
156 |  |
         public Configuration configuration() {
157 | 1 |
             return configuration;
158 |  |
         }
159 |  |
160 |  |
         public List<StepCandidate> listCandidates() {
161 | 23 |
             List<StepCandidate> candidates = new ArrayList<StepCandidate>();
162 | 23 |
             for (Method method : allMethods()) {
163 | 399 |
                 if (method.isAnnotationPresent(Given.class)) {
164 | 27 |
                     Given annotation = method.getAnnotation(Given.class);
165 | 27 |
                     String value = annotation.value();
166 | 27 |
                     int priority = annotation.priority();
167 | 27 |
                     addCandidatesFromVariants(candidates, method, GIVEN, value, priority);
168 | 26 |
                     addCandidatesFromAliases(candidates, method, GIVEN, priority);
169 |  |
                 }
170 | 398 |
                 if (method.isAnnotationPresent(When.class)) {
171 | 20 |
                     When annotation = method.getAnnotation(When.class);
172 | 20 |
                     String value = annotation.value();
173 | 20 |
                     int priority = annotation.priority();
174 | 20 |
                     addCandidatesFromVariants(candidates, method, WHEN, value, priority);
175 | 20 |
                     addCandidatesFromAliases(candidates, method, WHEN, priority);
176 |  |
                 }
177 | 398 |
                 if (method.isAnnotationPresent(Then.class)) {
178 | 16 |
                     Then annotation = method.getAnnotation(Then.class);
179 | 16 |
                     String value = annotation.value();
180 | 16 |
                     int priority = annotation.priority();
181 | 16 |
                     addCandidatesFromVariants(candidates, method, THEN, value, priority);
182 | 16 |
                     addCandidatesFromAliases(candidates, method, THEN, priority);
183 | 398 |
                 }
184 |  |
             }
185 | 22 |
             return candidates;
186 |  |
         }
187 |  |
188 |  |
         private void addCandidatesFromVariants(List<StepCandidate> candidates, Method method, StepType stepType, String value, int priority) {
189 | 75 |
             PatternVariantBuilder b = new PatternVariantBuilder(value);
190 | 75 |
             for (String variant : b.allVariants()) {
191 | 75 |
                 addCandidate(candidates, method, stepType, variant, priority);
192 |  |
             }
193 | 74 |
         }
194 |  |
195 |  |
         private void addCandidatesFromAliases(List<StepCandidate> candidates, Method method, StepType stepType, int priority) {
196 | 62 |
             if (method.isAnnotationPresent(Aliases.class)) {
197 | 3 |
                 String[] aliases = method.getAnnotation(Aliases.class).values();
198 | 9 |
                 for (String alias : aliases) {
199 | 6 |
                     addCandidatesFromVariants(candidates, method, stepType, alias, priority);
200 |  |
                 }
201 |  |
             }
202 | 62 |
             if (method.isAnnotationPresent(Alias.class)) {
203 | 6 |
                 String alias = method.getAnnotation(Alias.class).value();
204 | 6 |
                 addCandidatesFromVariants(candidates, method, stepType, alias, priority);
205 |  |
             }
206 | 62 |
         }
207 |  |
208 |  |
         private void addCandidate(List<StepCandidate> candidates, Method method, StepType stepType,
209 |  |
                 String stepPatternAsString, int priority) {
210 | 75 |
             checkForDuplicateCandidates(candidates, stepType, stepPatternAsString);
211 | 74 |
             StepCandidate candidate = createCandidate(method, stepType, stepPatternAsString, priority, configuration);
212 | 74 |
             candidate.useStepMonitor(configuration.stepMonitor());
213 | 74 |
             candidate.useParanamer(configuration.paranamer());
214 | 74 |
             candidate.doDryRun(configuration.storyControls().dryRun());
215 | 74 |
             if (method.isAnnotationPresent(Composite.class)) {
216 | 8 |
                 candidate.composedOf(method.getAnnotation(Composite.class).steps());
217 |  |
             }
218 | 74 |
             candidates.add(candidate);
219 | 74 |
         }
220 |  |
221 |  |
         private void checkForDuplicateCandidates(List<StepCandidate> candidates, StepType stepType, String patternAsString) {
222 | 75 |
             for (StepCandidate candidate : candidates) {
223 | 138 |
                 if (candidate.getStepType() == stepType && candidate.getPatternAsString().equals(patternAsString)) {
224 | 1 |
                     throw new DuplicateCandidateFound(stepType, patternAsString);
225 |  |
                 }
226 |  |
             }
227 | 74 |
         }
228 |  |
229 |  |
         private StepCandidate createCandidate(Method method, StepType stepType, String stepPatternAsString, int priority,
230 |  |
                 Configuration configuration) {
231 | 74 |
             return new StepCandidate(stepPatternAsString, priority, stepType, method, type, stepsFactory,
232 |  |
                     configuration.keywords(), configuration.stepPatternParser(), configuration.parameterConverters(), configuration.parameterControls());
233 |  |
         }
234 |  |
235 |  |
         public List<BeforeOrAfterStep> listBeforeOrAfterStories() {
236 | 3 |
             List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
237 | 3 |
             steps.addAll(stepsHaving(Stage.BEFORE, BeforeStories.class));
238 | 3 |
             steps.addAll(stepsHaving(Stage.AFTER, AfterStories.class));
239 | 3 |
             return steps;
240 |  |
         }
241 |  |
242 |  |
         public List<BeforeOrAfterStep> listBeforeOrAfterStory(boolean givenStory) {
243 | 10 |
             List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
244 | 10 |
             steps.addAll(stepsHaving(Stage.BEFORE, BeforeStory.class, givenStory));
245 | 10 |
             steps.addAll(stepsHaving(Stage.AFTER, AfterStory.class, givenStory));
246 | 10 |
             return steps;
247 |  |
         }
248 |  |
249 |  |
         public List<BeforeOrAfterStep> listBeforeOrAfterScenario(ScenarioType type) {
250 | 17 |
             List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
251 | 17 |
             steps.addAll(scenarioStepsHaving(type, Stage.BEFORE, BeforeScenario.class));
252 | 17 |
             steps.addAll(scenarioStepsHaving(type, Stage.AFTER, AfterScenario.class, ANY, SUCCESS, FAILURE));
253 | 17 |
             return steps;
254 |  |
         }
255 |  |
256 |  |
         private boolean runnableStoryStep(Annotation annotation, boolean givenStory) {
257 | 20 |
             boolean uponGivenStory = uponGivenStory(annotation);
258 | 20 |
             return uponGivenStory == givenStory;
259 |  |
         }
260 |  |
261 |  |
         private boolean uponGivenStory(Annotation annotation) {
262 | 20 |
             if (annotation instanceof BeforeStory) {
263 | 10 |
                 return ((BeforeStory) annotation).uponGivenStory();
264 | 10 |
             } else if (annotation instanceof AfterStory) {
265 | 10 |
                 return ((AfterStory) annotation).uponGivenStory();
266 |  |
             }
267 | 0 |
             return false;
268 |  |
         }
269 |  |
270 |  |
         private List<BeforeOrAfterStep> stepsHaving(Stage stage, Class<? extends Annotation> annotationClass) {
271 | 6 |
             List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
272 | 6 |
             for (Method method : methodsAnnotatedWith(annotationClass)) {
273 | 2 |
                 steps.add(createBeforeOrAfterStep(stage, method));
274 |  |
             }
275 | 6 |
             return steps;
276 |  |
         }
277 |  |
278 |  |
         private List<BeforeOrAfterStep> stepsHaving(Stage stage, Class<? extends Annotation> annotationClass,
279 |  |
                 boolean givenStory) {
280 | 20 |
             List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
281 | 20 |
             for (final Method method : methodsAnnotatedWith(annotationClass)) {
282 | 20 |
                 if (runnableStoryStep(method.getAnnotation(annotationClass), givenStory)) {
283 | 14 |
                     steps.add(createBeforeOrAfterStep(stage, method));
284 |  |
                 }
285 |  |
             }
286 | 20 |
             return steps;
287 |  |
         }
288 |  |
289 |  |
         private List<BeforeOrAfterStep> scenarioStepsHaving(ScenarioType type, Stage stage,
290 |  |
                 Class<? extends Annotation> annotationClass, Outcome... outcomes) {
291 | 34 |
             List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
292 | 34 |
             for (Method method : methodsAnnotatedWith(annotationClass)) {
293 | 42 |
                 ScenarioType scenarioType = scenarioType(method, annotationClass);
294 | 42 |
                 if (type == scenarioType) {
295 | 32 |
                     if (stage == Stage.BEFORE) {
296 | 13 |
                         steps.add(createBeforeOrAfterStep(stage, method));
297 |  |
                     }
298 | 32 |
                     if (stage == Stage.AFTER) {
299 | 19 |
                         Outcome scenarioOutcome = scenarioOutcome(method, annotationClass);
300 | 76 |
                         for (Outcome outcome : outcomes) {
301 | 57 |
                             if (outcome.equals(scenarioOutcome)) {
302 | 19 |
                                 steps.add(createBeforeOrAfterStep(stage, method, outcome));
303 |  |
                             }
304 |  |
                         }
305 |  |
                     }
306 |  |
                 }
307 | 42 |
             }
308 | 34 |
             return steps;
309 |  |
         }
310 |  |
311 |  |
         private ScenarioType scenarioType(Method method, Class<? extends Annotation> annotationClass) {
312 | 42 |
             if (annotationClass.isAssignableFrom(BeforeScenario.class)) {
313 | 17 |
                 return ((BeforeScenario) method.getAnnotation(annotationClass)).uponType();
314 |  |
             }
315 | 25 |
             if (annotationClass.isAssignableFrom(AfterScenario.class)) {
316 | 25 |
                 return ((AfterScenario) method.getAnnotation(annotationClass)).uponType();
317 |  |
             }
318 | 0 |
             return ScenarioType.NORMAL;
319 |  |
         }
320 |  |
321 |  |
         private Outcome scenarioOutcome(Method method, Class<? extends Annotation> annotationClass) {
322 | 19 |
             if (annotationClass.isAssignableFrom(AfterScenario.class)) {
323 | 19 |
                 return ((AfterScenario) method.getAnnotation(annotationClass)).uponOutcome();
324 |  |
             }
325 | 0 |
             return Outcome.ANY;
326 |  |
         }
327 |  |
328 |  |
         private BeforeOrAfterStep createBeforeOrAfterStep(Stage stage, Method method) {
329 | 29 |
             return createBeforeOrAfterStep(stage, method, Outcome.ANY);
330 |  |
         }
331 |  |
332 |  |
         private BeforeOrAfterStep createBeforeOrAfterStep(Stage stage, Method method, Outcome outcome) {
333 | 48 |
             return new BeforeOrAfterStep(stage, method, outcome, new StepCreator(type, stepsFactory,
334 |  |
                     configuration.parameterConverters(), configuration.parameterControls(), null, configuration.stepMonitor()));
335 |  |
         }
336 |  |
337 |  |
         private List<Method> allMethods() {
338 | 83 |
             return asList(type.getMethods());
339 |  |
         }
340 |  |
341 |  |
         private List<Method> methodsAnnotatedWith(Class<? extends Annotation> annotationClass) {
342 | 60 |
             List<Method> annotated = new ArrayList<Method>();
343 | 60 |
             for (Method method : allMethods()) {
344 | 1156 |
                 if (method.isAnnotationPresent(annotationClass)) {
345 | 64 |
                     annotated.add(method);
346 |  |
                 }
347 |  |
             }
348 | 60 |
             return annotated;
349 |  |
         }
350 |  |
351 |  |
         @SuppressWarnings("serial")
352 |  |
         public static class DuplicateCandidateFound extends RuntimeException {
353 |  |
354 |  |
             public DuplicateCandidateFound(StepType stepType, String patternAsString) {
355 | 1 |
                 super(stepType + " " + patternAsString);
356 | 1 |
             }
357 |  |
358 |  |
         }
359 |  |
360 |  |
         @Override
361 |  |
         public String toString() {
362 | 4 |
             return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(instance()).toString();
363 |  |
         }
364 |  |
365 |  |
     }