AnnotatedPathRunner.java

package org.jbehave.core.junit;

import java.util.List;

import org.jbehave.core.annotations.UsingPaths;
import org.jbehave.core.configuration.AnnotationBuilder;
import org.jbehave.core.embedder.DelegatingEmbedderMonitor;
import org.jbehave.core.embedder.Embedder;
import org.jbehave.core.embedder.EmbedderMonitor;
import org.jbehave.core.io.StoryNameResolver;
import org.jbehave.core.io.UnderscoredToCapitalized;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;

/**
 * A JUnit {@link Runner} that uses a {@link UsingPaths} annotation to specify
 * which story paths to run and uses the {@link RunNotifier} to provide a
 * {@link Description} for each. The story description uses a
 * {@link StoryNameResolver} (overridable via the {@link #storyNameResolver()}
 * method) to resolve the story path to a name.
 */
public class AnnotatedPathRunner extends AnnotatedEmbedderRunner {

    private final AnnotationBuilder annotationBuilder;
    private final StoryNameResolver nameResolver;
    private final List<String> paths;

    /**
     * Class constructor.
     * 
     * @param annotatedClass the annotated {@link Class}.
     * @throws InitializationError if an error occurs.
     */
    public AnnotatedPathRunner(Class<?> annotatedClass) throws InitializationError {
        super(annotatedClass);
        this.annotationBuilder = annotationBuilder();
        this.nameResolver = storyNameResolver();
        this.paths = annotationBuilder.findPaths();
    }

    protected StoryNameResolver storyNameResolver() {
        return new UnderscoredToCapitalized();
    }

    @Override
    public Description getDescription() {
        Description description = Description.createSuiteDescription(testClass());
        for (String path : paths) {
            description.addChild(createDescriptionForPath(path));
        }

        return description;
    }

    private Description createDescriptionForPath(String path) {
        String name = nameResolver.resolveName(path);
        return Description.createTestDescription(testClass(), name);
    }

    @Override
    protected void collectInitializationErrors(List<Throwable> errors) {
        // overridden to avoid JUnit-specific errors
    }

    @Override
    protected void validateInstanceMethods(List<Throwable> errors) {
        // overridden to avoid JUnit-specific errors
    }

    @Override
    public void run(RunNotifier notifier) {
        Embedder embedder = annotationBuilder.buildEmbedder();
        NotifierEmbedderMonitor notifierEmbedderMonitor = new NotifierEmbedderMonitor(embedder.embedderMonitor(),
                notifier);
        embedder.useEmbedderMonitor(notifierEmbedderMonitor);

        try {
            embedder.runStoriesAsPaths(paths);
        } finally {
            notifierEmbedderMonitor.storyFinished();
        }
    }

    /**
     * {@link EmbedderMonitor} that reports story updates to a
     * {@link RunNotifier}.
     */
    private final class NotifierEmbedderMonitor extends DelegatingEmbedderMonitor {
        private final RunNotifier notifier;
        private Description currentStory;

        /**
         * Creates a NotifierEmbedderMonitor
         * 
         * @param delegate the EmbedderMonitor delegate
         * @param notifier the RunNotifier
         */
        private NotifierEmbedderMonitor(EmbedderMonitor delegate, RunNotifier notifier) {
            super(delegate);
            this.notifier = notifier;
        }

        @Override
        public void runningStory(String path) {
            super.runningStory(path);
            storyFinished();
            currentStory = createDescriptionForPath(path);
            notifier.fireTestStarted(currentStory);
        }

        @Override
        public void storyFailed(String path, Throwable cause) {
            super.storyFailed(path, cause);
            notifier.fireTestFailure(new Failure(currentStory, cause));
            notifier.fireTestFinished(currentStory);
            currentStory = null;
        }

        /**
         * Finishes the last story.
         */
        private void storyFinished() {
            if (currentStory == null) {
                return;
            }
            notifier.fireTestFinished(currentStory);
        }
    }
}