Coverage Report - org.jbehave.core.steps.Steps
 
Classes in this File Line Coverage Branch Coverage Complexity
Steps
92%
113/122
94%
53/56
2.179
Steps$DuplicateCandidateFound
100%
2/2
N/A
2.179
 
 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  92
         this(new MostUsefulConfiguration());
 106  92
     }
 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  99
     public Steps(Configuration configuration) {
 115  99
         this.configuration = configuration;
 116  99
         this.type = this.getClass();
 117  99
         this.stepsFactory = new InstanceStepsFactory(configuration, this);
 118  99
     }
 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  15
     public Steps(Configuration configuration, Class<?> type, InjectableStepsFactory stepsFactory) {
 143  15
         this.configuration = configuration;
 144  15
         this.type = type;
 145  15
         this.stepsFactory = stepsFactory;
 146  15
     }
 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  20
         List<StepCandidate> candidates = new ArrayList<StepCandidate>();
 162  20
         for (Method method : allMethods()) {
 163  348
             if (method.isAnnotationPresent(Given.class)) {
 164  20
                 Given annotation = method.getAnnotation(Given.class);
 165  20
                 String value = annotation.value();
 166  20
                 int priority = annotation.priority();
 167  20
                 addCandidate(candidates, method, GIVEN, value, priority);
 168  19
                 addCandidatesFromAliases(candidates, method, GIVEN, priority);
 169  
             }
 170  347
             if (method.isAnnotationPresent(When.class)) {
 171  17
                 When annotation = method.getAnnotation(When.class);
 172  17
                 String value = annotation.value();
 173  17
                 int priority = annotation.priority();
 174  17
                 addCandidate(candidates, method, WHEN, value, priority);
 175  17
                 addCandidatesFromAliases(candidates, method, WHEN, priority);
 176  
             }
 177  347
             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
                 addCandidate(candidates, method, THEN, value, priority);
 182  16
                 addCandidatesFromAliases(candidates, method, THEN, priority);
 183  347
             }
 184  
         }
 185  19
         return candidates;
 186  
     }
 187  
 
 188  
     private void addCandidate(List<StepCandidate> candidates, Method method, StepType stepType,
 189  
             String stepPatternAsString, int priority) {
 190  65
         checkForDuplicateCandidates(candidates, stepType, stepPatternAsString);
 191  64
         StepCandidate candidate = createCandidate(method, stepType, stepPatternAsString, priority, configuration);
 192  64
         candidate.useStepMonitor(configuration.stepMonitor());
 193  64
         candidate.useParanamer(configuration.paranamer());
 194  64
         candidate.doDryRun(configuration.storyControls().dryRun());
 195  64
         if (method.isAnnotationPresent(Composite.class)) {
 196  5
             candidate.composedOf(method.getAnnotation(Composite.class).steps());
 197  
         }
 198  64
         candidates.add(candidate);
 199  64
     }
 200  
 
 201  
     private void checkForDuplicateCandidates(List<StepCandidate> candidates, StepType stepType, String patternAsString) {
 202  65
         for (StepCandidate candidate : candidates) {
 203  116
             if (candidate.getStepType() == stepType && candidate.getPatternAsString().equals(patternAsString)) {
 204  1
                 throw new DuplicateCandidateFound(stepType, patternAsString);
 205  
             }
 206  
         }
 207  64
     }
 208  
 
 209  
     private void addCandidatesFromAliases(List<StepCandidate> candidates, Method method, StepType stepType, int priority) {
 210  52
         if (method.isAnnotationPresent(Aliases.class)) {
 211  3
             String[] aliases = method.getAnnotation(Aliases.class).values();
 212  9
             for (String alias : aliases) {
 213  6
                 addCandidate(candidates, method, stepType, alias, priority);
 214  
             }
 215  
         }
 216  52
         if (method.isAnnotationPresent(Alias.class)) {
 217  6
             String alias = method.getAnnotation(Alias.class).value();
 218  6
             addCandidate(candidates, method, stepType, alias, priority);
 219  
         }
 220  52
     }
 221  
 
 222  
     private StepCandidate createCandidate(Method method, StepType stepType, String stepPatternAsString, int priority,
 223  
             Configuration configuration) {
 224  64
         return new StepCandidate(stepPatternAsString, priority, stepType, method, type, stepsFactory,
 225  
                 configuration.keywords(), configuration.stepPatternParser(), configuration.parameterConverters());
 226  
     }
 227  
 
 228  
     public List<BeforeOrAfterStep> listBeforeOrAfterStories() {
 229  3
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 230  3
         steps.addAll(stepsHaving(Stage.BEFORE, BeforeStories.class));
 231  3
         steps.addAll(stepsHaving(Stage.AFTER, AfterStories.class));
 232  3
         return steps;
 233  
     }
 234  
 
 235  
     public List<BeforeOrAfterStep> listBeforeOrAfterStory(boolean givenStory) {
 236  8
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 237  8
         steps.addAll(stepsHaving(Stage.BEFORE, BeforeStory.class, givenStory));
 238  8
         steps.addAll(stepsHaving(Stage.AFTER, AfterStory.class, givenStory));
 239  8
         return steps;
 240  
     }
 241  
 
 242  
     public List<BeforeOrAfterStep> listBeforeOrAfterScenario(ScenarioType type) {
 243  14
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 244  14
         steps.addAll(scenarioStepsHaving(type, Stage.BEFORE, BeforeScenario.class));
 245  14
         steps.addAll(scenarioStepsHaving(type, Stage.AFTER, AfterScenario.class, ANY, SUCCESS, FAILURE));
 246  14
         return steps;
 247  
     }
 248  
 
 249  
     private boolean runnableStoryStep(Annotation annotation, boolean givenStory) {
 250  20
         boolean uponGivenStory = uponGivenStory(annotation);
 251  20
         return uponGivenStory == givenStory;
 252  
     }
 253  
 
 254  
     private boolean uponGivenStory(Annotation annotation) {
 255  20
         if (annotation instanceof BeforeStory) {
 256  10
             return ((BeforeStory) annotation).uponGivenStory();
 257  10
         } else if (annotation instanceof AfterStory) {
 258  10
             return ((AfterStory) annotation).uponGivenStory();
 259  
         }
 260  0
         return false;
 261  
     }
 262  
 
 263  
     private List<BeforeOrAfterStep> stepsHaving(Stage stage, Class<? extends Annotation> annotationClass) {
 264  6
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 265  6
         for (Method method : methodsAnnotatedWith(annotationClass)) {
 266  2
             steps.add(createBeforeOrAfterStep(stage, method));
 267  
         }
 268  6
         return steps;
 269  
     }
 270  
 
 271  
     private List<BeforeOrAfterStep> stepsHaving(Stage stage, Class<? extends Annotation> annotationClass,
 272  
             boolean givenStory) {
 273  16
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 274  16
         for (final Method method : methodsAnnotatedWith(annotationClass)) {
 275  20
             if (runnableStoryStep(method.getAnnotation(annotationClass), givenStory)) {
 276  14
                 steps.add(createBeforeOrAfterStep(stage, method));
 277  
             }
 278  
         }
 279  16
         return steps;
 280  
     }
 281  
 
 282  
     private List<BeforeOrAfterStep> scenarioStepsHaving(ScenarioType type, Stage stage,
 283  
             Class<? extends Annotation> annotationClass, Outcome... outcomes) {
 284  28
         List<BeforeOrAfterStep> steps = new ArrayList<BeforeOrAfterStep>();
 285  28
         for (Method method : methodsAnnotatedWith(annotationClass)) {
 286  42
             ScenarioType scenarioType = scenarioType(method, annotationClass);
 287  42
             if (type == scenarioType) {
 288  32
                 if (stage == Stage.BEFORE) {
 289  14
                     steps.add(createBeforeOrAfterStep(stage, method));
 290  
                 }
 291  32
                 if (stage == Stage.AFTER) {
 292  18
                     Outcome scenarioOutcome = scenarioOutcome(method, annotationClass);
 293  72
                     for (Outcome outcome : outcomes) {
 294  54
                         if (outcome.equals(scenarioOutcome)) {
 295  18
                             steps.add(createBeforeOrAfterStep(stage, method, outcome));
 296  
                         }
 297  
                     }
 298  
                 }
 299  
             }
 300  42
         }
 301  28
         return steps;
 302  
     }
 303  
 
 304  
     private ScenarioType scenarioType(Method method, Class<? extends Annotation> annotationClass) {
 305  42
         if (annotationClass.isAssignableFrom(BeforeScenario.class)) {
 306  18
             return ((BeforeScenario) method.getAnnotation(annotationClass)).uponType();
 307  
         }
 308  24
         if (annotationClass.isAssignableFrom(AfterScenario.class)) {
 309  24
             return ((AfterScenario) method.getAnnotation(annotationClass)).uponType();
 310  
         }
 311  0
         return ScenarioType.NORMAL;
 312  
     }
 313  
 
 314  
     private Outcome scenarioOutcome(Method method, Class<? extends Annotation> annotationClass) {
 315  18
         if (annotationClass.isAssignableFrom(AfterScenario.class)) {
 316  18
             return ((AfterScenario) method.getAnnotation(annotationClass)).uponOutcome();
 317  
         }
 318  0
         return Outcome.ANY;
 319  
     }
 320  
 
 321  
     private BeforeOrAfterStep createBeforeOrAfterStep(Stage stage, Method method) {
 322  30
         return createBeforeOrAfterStep(stage, method, Outcome.ANY);
 323  
     }
 324  
 
 325  
     private BeforeOrAfterStep createBeforeOrAfterStep(Stage stage, Method method, Outcome outcome) {
 326  48
         return new BeforeOrAfterStep(stage, method, outcome, new StepCreator(type, stepsFactory,
 327  
                 configuration.parameterConverters(), null, configuration.stepMonitor()));
 328  
     }
 329  
 
 330  
     private List<Method> allMethods() {
 331  70
         return asList(type.getMethods());
 332  
     }
 333  
 
 334  
     private List<Method> methodsAnnotatedWith(Class<? extends Annotation> annotationClass) {
 335  50
         List<Method> annotated = new ArrayList<Method>();
 336  50
         for (Method method : allMethods()) {
 337  1044
             if (method.isAnnotationPresent(annotationClass)) {
 338  64
                 annotated.add(method);
 339  
             }
 340  
         }
 341  50
         return annotated;
 342  
     }
 343  
 
 344  
     @SuppressWarnings("serial")
 345  
     public static class DuplicateCandidateFound extends RuntimeException {
 346  
 
 347  
         public DuplicateCandidateFound(StepType stepType, String patternAsString) {
 348  1
             super(stepType + " " + patternAsString);
 349  1
         }
 350  
 
 351  
     }
 352  
 
 353  
     @Override
 354  
     public String toString() {
 355  4
         return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(instance()).toString();
 356  
     }
 357  
 
 358  
 }