Coverage Report - org.jbehave.core.steps.Steps
 
Classes in this File Line Coverage Branch Coverage Complexity
Steps
93%
122/131
94%
55/58
2.172
Steps$DuplicateCandidateFound
100%
2/2
N/A
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  106
         this(new MostUsefulConfiguration());
 106  106
     }
 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  115
     public Steps(Configuration configuration) {
 115  115
         this.configuration = configuration;
 116  115
         this.type = this.getClass();
 117  115
         this.stepsFactory = new InstanceStepsFactory(configuration, this);
 118  115
     }
 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  24
     public Steps(Configuration configuration, Class<?> type, InjectableStepsFactory stepsFactory) {
 143  24
         this.configuration = configuration;
 144  24
         this.type = type;
 145  24
         this.stepsFactory = stepsFactory;
 146  24
     }
 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  32
         List<StepCandidate> candidates = new ArrayList<StepCandidate>();
 162  32
         for (Method method : allMethods()) {
 163  739
             if (method.isAnnotationPresent(Given.class)) {
 164  33
                 Given annotation = method.getAnnotation(Given.class);
 165  33
                 String value = annotation.value();
 166  33
                 int priority = annotation.priority();
 167  33
                 addCandidatesFromVariants(candidates, method, GIVEN, value, priority);
 168  32
                 addCandidatesFromAliases(candidates, method, GIVEN, priority);
 169  
             }
 170  738
             if (method.isAnnotationPresent(When.class)) {
 171  23
                 When annotation = method.getAnnotation(When.class);
 172  23
                 String value = annotation.value();
 173  23
                 int priority = annotation.priority();
 174  23
                 addCandidatesFromVariants(candidates, method, WHEN, value, priority);
 175  23
                 addCandidatesFromAliases(candidates, method, WHEN, priority);
 176  
             }
 177  738
             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  
             }
 184  738
         }
 185  31
         return candidates;
 186  
     }
 187  
 
 188  
     private void addCandidatesFromVariants(List<StepCandidate> candidates, Method method, StepType stepType, String value, int priority) {
 189  84
         PatternVariantBuilder b = new PatternVariantBuilder(value);
 190  84
         for (String variant : b.allVariants()) {
 191  84
             addCandidate(candidates, method, stepType, variant, priority);
 192  83
         }
 193  83
     }
 194  
     
 195  
     private void addCandidatesFromAliases(List<StepCandidate> candidates, Method method, StepType stepType, int priority) {
 196  71
         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  71
         if (method.isAnnotationPresent(Alias.class)) {
 203  6
             String alias = method.getAnnotation(Alias.class).value();
 204  6
             addCandidatesFromVariants(candidates, method, stepType, alias, priority);
 205  
         }
 206  71
     }
 207  
 
 208  
     private void addCandidate(List<StepCandidate> candidates, Method method, StepType stepType,
 209  
             String stepPatternAsString, int priority) {
 210  84
         checkForDuplicateCandidates(candidates, stepType, stepPatternAsString);
 211  83
         StepCandidate candidate = createCandidate(method, stepType, stepPatternAsString, priority, configuration);
 212  83
         candidate.useStepMonitor(configuration.stepMonitor());
 213  83
         candidate.useParanamer(configuration.paranamer());
 214  83
         candidate.doDryRun(configuration.storyControls().dryRun());
 215  83
         if (method.isAnnotationPresent(Composite.class)) {
 216  8
             candidate.composedOf(method.getAnnotation(Composite.class).steps());
 217  
         }
 218  83
         candidates.add(candidate);
 219  83
     }
 220  
 
 221  
     private void checkForDuplicateCandidates(List<StepCandidate> candidates, StepType stepType, String patternAsString) {
 222  84
         for (StepCandidate candidate : candidates) {
 223  138
             if (candidate.getStepType() == stepType && candidate.getPatternAsString().equals(patternAsString)) {
 224  1
                 throw new DuplicateCandidateFound(stepType, patternAsString);
 225  
             }
 226  137
         }
 227  83
     }
 228  
 
 229  
     private StepCandidate createCandidate(Method method, StepType stepType, String stepPatternAsString, int priority,
 230  
             Configuration configuration) {
 231  83
         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  6
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 237  6
         steps.addAll(stepsHaving(Stage.BEFORE, BeforeStories.class));
 238  6
         steps.addAll(stepsHaving(Stage.AFTER, AfterStories.class));
 239  6
         return steps;
 240  
     }
 241  
 
 242  
     public List<BeforeOrAfterStep> listBeforeOrAfterStory(boolean givenStory) {
 243  14
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 244  14
         steps.addAll(stepsHaving(Stage.BEFORE, BeforeStory.class, givenStory));
 245  14
         steps.addAll(stepsHaving(Stage.AFTER, AfterStory.class, givenStory));
 246  14
         return steps;
 247  
     }
 248  
 
 249  
     public List<BeforeOrAfterStep> listBeforeOrAfterScenario(ScenarioType type) {
 250  29
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 251  29
         steps.addAll(scenarioStepsHaving(type, Stage.BEFORE, BeforeScenario.class));
 252  29
         steps.addAll(scenarioStepsHaving(type, Stage.AFTER, AfterScenario.class, ANY, SUCCESS, FAILURE));
 253  29
         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  12
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 272  12
         for (Method method : methodsAnnotatedWith(annotationClass)) {
 273  2
             steps.add(createBeforeOrAfterStep(stage, method));
 274  2
         }
 275  12
         return steps;
 276  
     }
 277  
 
 278  
     private List<BeforeOrAfterStep> stepsHaving(Stage stage, Class<? extends Annotation> annotationClass,
 279  
             boolean givenStory) {
 280  28
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 281  28
         for (final Method method : methodsAnnotatedWith(annotationClass)) {
 282  20
             if (runnableStoryStep(method.getAnnotation(annotationClass), givenStory)) {
 283  14
                 steps.add(createBeforeOrAfterStep(stage, method));
 284  
             }
 285  20
         }
 286  28
         return steps;
 287  
     }
 288  
 
 289  
     private List<BeforeOrAfterStep> scenarioStepsHaving(ScenarioType type, Stage stage,
 290  
             Class<? extends Annotation> annotationClass, Outcome... outcomes) {
 291  58
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 292  58
         for (Method method : methodsAnnotatedWith(annotationClass)) {
 293  58
             ScenarioType scenarioType = scenarioType(method, annotationClass);
 294  58
             if (type == scenarioType) {
 295  34
                 if (stage == Stage.BEFORE) {
 296  14
                     steps.add(createBeforeOrAfterStep(stage, method));
 297  
                 }
 298  34
                 if (stage == Stage.AFTER) {
 299  20
                     Outcome scenarioOutcome = scenarioOutcome(method, annotationClass);
 300  80
                     for (Outcome outcome : outcomes) {
 301  60
                         if (outcome.equals(scenarioOutcome)) {
 302  20
                             steps.add(createBeforeOrAfterStep(stage, method, outcome));
 303  
                         }
 304  
                     }
 305  
                 }
 306  
             }
 307  58
         }
 308  58
         return steps;
 309  
     }
 310  
 
 311  
     private ScenarioType scenarioType(Method method, Class<? extends Annotation> annotationClass) {
 312  58
         if (annotationClass.isAssignableFrom(BeforeScenario.class)) {
 313  24
             return ((BeforeScenario) method.getAnnotation(annotationClass)).uponType();
 314  
         }
 315  34
         if (annotationClass.isAssignableFrom(AfterScenario.class)) {
 316  34
             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  20
         if (annotationClass.isAssignableFrom(AfterScenario.class)) {
 323  20
             return ((AfterScenario) method.getAnnotation(annotationClass)).uponOutcome();
 324  
         }
 325  0
         return Outcome.ANY;
 326  
     }
 327  
 
 328  
     private BeforeOrAfterStep createBeforeOrAfterStep(Stage stage, Method method) {
 329  30
         return createBeforeOrAfterStep(stage, method, Outcome.ANY);
 330  
     }
 331  
 
 332  
     private BeforeOrAfterStep createBeforeOrAfterStep(Stage stage, Method method, Outcome outcome) {
 333  50
         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  130
         return asList(type.getMethods());
 339  
     }
 340  
 
 341  
     private List<Method> methodsAnnotatedWith(Class<? extends Annotation> annotationClass) {
 342  98
         List<Method> annotated = new ArrayList<Method>();
 343  98
         for (Method method : allMethods()) {
 344  2630
             if (method.isAnnotationPresent(annotationClass)) {
 345  80
                 annotated.add(method);
 346  
             }
 347  2630
         }
 348  98
         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  
 }