diff --git a/README.md b/README.md index 3a55aeb6..59255a98 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,275 @@ been fixed already in the latest snapshot release. And, *of course*, we'd also very much appreciate pull requests for fixes that you have already made yourself. After all, this is where open source shines most :) +## spring support +``` +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.jbehave.core.ConfigurableEmbedder; +import org.jbehave.core.configuration.Configuration; +import org.jbehave.core.embedder.Embedder; +import org.jbehave.core.embedder.EmbedderControls; +import org.jbehave.core.embedder.PerformableTree; +import org.jbehave.core.embedder.PerformableTree.RunContext; +import org.jbehave.core.failures.BatchFailures; +import org.jbehave.core.junit.JUnitStories; +import org.jbehave.core.junit.JUnitStory; +import org.jbehave.core.model.Story; +import org.jbehave.core.reporters.StoryReporterBuilder; +import org.jbehave.core.steps.CandidateSteps; +import org.jbehave.core.steps.InjectableStepsFactory; +import org.jbehave.core.steps.NullStepMonitor; +import org.jbehave.core.steps.StepMonitor; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.github.valfirst.jbehave.junit.monitoring.JUnitDescriptionGenerator; +import com.github.valfirst.jbehave.junit.monitoring.JUnitScenarioReporter; + +public class SpringReportingRunner extends SpringJUnit4ClassRunner{ + + + private Embedder configuredEmbedder; + private Configuration configuration; + private int numberOfTestCases; + private Description rootDescription; + private ConfigurableEmbedder configurableEmbedder; + + public SpringReportingRunner(Class testClass) + throws InitializationError, ReflectiveOperationException { + super(testClass); + super.getTestContextManager(); + try { + configurableEmbedder =(ConfigurableEmbedder) super.createTest(); + } catch (Exception e) { + throw new RuntimeException(e); + } + configuredEmbedder = configurableEmbedder.configuredEmbedder(); + configuration = configuredEmbedder.configuration(); + + List storyPaths = getStoryPaths(testClass); + + StepMonitor originalStepMonitor = configuration.stepMonitor(); + configuration.useStepMonitor(new NullStepMonitor()); + List storyDescriptions = buildDescriptionFromStories(storyPaths); + configuration.useStepMonitor(originalStepMonitor); + + rootDescription = Description.createSuiteDescription(testClass); + for (Description storyDescription : storyDescriptions) { + rootDescription.addChild(storyDescription); + } + } + + @Override + public Description getDescription() { + return rootDescription; + } + + @Override + public int testCount() { + return numberOfTestCases; + } + + /** + * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)} + * on each object returned by {@link #getChildren()} (subject to any imposed + * filter and sort) + */ + @Override + protected Statement childrenInvoker(final RunNotifier notifier) { + return new Statement() { + @Override + public void evaluate() { + JUnitScenarioReporter junitReporter = new JUnitScenarioReporter( + notifier, numberOfTestCases, rootDescription, configuration.keywords()); + // tell the reporter how to handle pending steps + junitReporter.usePendingStepStrategy(configuration + .pendingStepStrategy()); + + addToStoryReporterFormats(junitReporter); + + try { + configurableEmbedder.run(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + }; + } + + public static EmbedderControls recommendedControls(Embedder embedder) { + return embedder.embedderControls() + // don't throw an exception on generating reports for failing stories + .doIgnoreFailureInView(true) + // don't throw an exception when a story failed + .doIgnoreFailureInStories(true) + // .doVerboseFailures(true) + .useThreads(1); + } + + private List getStoryPaths(Class testClass) + throws ReflectiveOperationException { + if (JUnitStories.class.isAssignableFrom(testClass)) { + return getStoryPathsFromJUnitStories(testClass); + } else if (JUnitStory.class.isAssignableFrom(testClass)) { + return Arrays.asList(configuration.storyPathResolver().resolve(testClass)); + } else { + throw new IllegalArgumentException( + "Only ConfigurableEmbedder of types JUnitStory and JUnitStories is supported"); + } + } + + @SuppressWarnings("unchecked") + private List getStoryPathsFromJUnitStories( + Class testClass) + throws ReflectiveOperationException { + Method method = makeStoryPathsMethodPublic(testClass); + return ((List) method.invoke(configurableEmbedder, (Object[]) null)); + } + + @SuppressWarnings("unchecked") + private static Method makeStoryPathsMethodPublic(Class clazz) + throws NoSuchMethodException { + try { + Method method = clazz.getDeclaredMethod("storyPaths", (Class[]) null); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + Class superclass = clazz.getSuperclass(); + if (superclass != null && ConfigurableEmbedder.class.isAssignableFrom(superclass)) { + return makeStoryPathsMethodPublic((Class) superclass); + } + throw e; + } + } + + private List getCandidateSteps() { + List candidateSteps; + InjectableStepsFactory stepsFactory = configurableEmbedder.stepsFactory(); + if (stepsFactory != null) { + candidateSteps = stepsFactory.createCandidateSteps(); + } else { + candidateSteps = configuredEmbedder.candidateSteps(); + if (candidateSteps == null || candidateSteps.isEmpty()) { + candidateSteps = configuredEmbedder.stepsFactory().createCandidateSteps(); + } + } + return candidateSteps; + } + + private void addToStoryReporterFormats(JUnitScenarioReporter junitReporter) { + StoryReporterBuilder storyReporterBuilder = configuration + .storyReporterBuilder(); + StoryReporterBuilder.ProvidedFormat junitReportFormat = new StoryReporterBuilder.ProvidedFormat( + junitReporter); + storyReporterBuilder.withFormats(junitReportFormat); + } + + private List buildDescriptionFromStories(List storyPaths) { + JUnitDescriptionGenerator descriptionGenerator = new JUnitDescriptionGenerator( + getCandidateSteps(), configuration); + List storyDescriptions = new ArrayList<>(); + + addSuite(storyDescriptions, "BeforeStories"); + storyDescriptions.addAll(descriptionGenerator.createDescriptionFrom(createPerformableTree(storyPaths))); + addSuite(storyDescriptions, "AfterStories"); + + numberOfTestCases += descriptionGenerator.getTestCases(); + + return storyDescriptions; + } + + private PerformableTree createPerformableTree(List storyPaths) { + BatchFailures failures = new BatchFailures(configuredEmbedder.embedderControls().verboseFailures()); + PerformableTree performableTree = new PerformableTree(); + RunContext context = performableTree.newRunContext(configuration, configuredEmbedder.stepsFactory(), + configuredEmbedder.embedderMonitor(), configuredEmbedder.metaFilter(), failures); + performableTree.addStories(context, storiesOf(performableTree, storyPaths)); + return performableTree; + } + + private List storiesOf(PerformableTree performableTree, List storyPaths) { + List stories = new ArrayList<>(); + for (String storyPath : storyPaths) { + stories.add(performableTree.storyOfPath(configuration, storyPath)); + } + return stories; + } + + private void addSuite(List storyDescriptions, String name) { + storyDescriptions.add(Description.createTestDescription(Object.class, + name)); + numberOfTestCases++; + } +} +``` +### spring support useage +``` +import static org.jbehave.core.io.CodeLocations.codeLocationFromPath; + +import java.util.List; + +import org.jbehave.core.configuration.Configuration; +import org.jbehave.core.configuration.MostUsefulConfiguration; +import org.jbehave.core.io.StoryFinder; +import org.jbehave.core.junit.JUnitStories; +import org.jbehave.core.reporters.CrossReference; +import org.jbehave.core.reporters.Format; +import org.jbehave.core.reporters.StoryReporterBuilder; +import org.jbehave.core.steps.InjectableStepsFactory; +import org.jbehave.core.steps.spring.SpringStepsFactory; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ActiveProfiles; + +@RunWith(SpringReportingRunner.class) +@SpringBootTest(properties = { "zookeeper.address=192.168.1.14:12181", "application.host=127.0.0.1", + "application.port=90", "profile=dev" }) +@AutoConfigureMockMvc +@ActiveProfiles(profiles = "dev") +public class Test2 extends JUnitStories { + @Autowired + protected ApplicationContext context; + @Override + public InjectableStepsFactory stepsFactory() { + if (super.hasStepsFactory()) { + return super.stepsFactory(); + } + return new SpringStepsFactory(configuration(), context); + } + + @Override + public Configuration configuration() { + if (super.hasConfiguration()) { + return super.configuration(); + } + Configuration configuration = new MostUsefulConfiguration().useStoryReporterBuilder(new StoryReporterBuilder() + .withDefaultFormats().withFormats(Format.CONSOLE, Format.TXT, Format.HTML, Format.XML) + .withCrossReference(new CrossReference()).withFailureTrace(true)); + return configuration; + } + + @Override + protected List storyPaths() { + List storyPaths = new StoryFinder().findPaths(codeLocationFromPath("src/test/resources"), "**/*.story", + ""); + return storyPaths; + } + + + +} +``` Credits ================================= This project is based on the original implementation by Mark Burnett, located at http://code.google.com/p/jbehave-junit-monitor/ which worked with earlier versions of JBehave.