Skip to content

Instantly share code, notes, and snippets.

@eleco
Created December 1, 2013 19:30
Show Gist options
  • Save eleco/7739765 to your computer and use it in GitHub Desktop.
Save eleco/7739765 to your computer and use it in GitHub Desktop.
jbehave spring example
package foo.bar;
public class Trade {
int price;
Trade (int price){
this.price = price;
}
}
package foo.bar;
import org.springframework.stereotype.Component;
@Component
public class TradeService {
public boolean validate(Trade trade) {
return (trade.price>0);
}
}
package foo.bar;
import org.jbehave.core.configuration.Keywords;
import org.jbehave.core.reporters.*;
import java.io.File;
public class CustomHtmlOutput extends HtmlTemplateOutput {
public CustomHtmlOutput (File file, Keywords keywords){
super(file, keywords, new FreemarkerProcessor(CustomHtmlOutput.class),"custom-html-output.ftl");
}
public static final Format FORMAT = new Format("HTML"){
@Override
public StoryReporter createStoryReporter(FilePrintStreamFactory factory, StoryReporterBuilder storyReporterBuilder){
factory.useConfiguration(storyReporterBuilder.fileConfiguration("html"));
return new CustomHtmlOutput(factory.getOutputFile(),storyReporterBuilder.keywords()) ;
}
};
}
package foo.bar;
import org.jbehave.core.InjectableEmbedder;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.io.StoryFinder;
import org.jbehave.core.steps.spring.SpringStepsFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.jbehave.core.io.CodeLocations.codeLocationFromClass;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-config.xml"})
public class JBehaveRunner extends InjectableEmbedder {
protected java.util.List<String> storyPaths (){
return new StoryFinder().findPaths(codeLocationFromClass(this.getClass()), "*.story", "") ;
}
@Autowired
public ApplicationContext applicationContext;
@Test
public void run() throws Throwable{
Configuration configuration = injectedEmbedder().configuration();
configuration.useStoryReporterBuilder(configuration.storyReporterBuilder().withFormats(CustomHtmlOutput.FORMAT));
injectedEmbedder().useCandidateSteps(new SpringStepsFactory(injectedEmbedder().configuration(),applicationContext).createCandidateSteps());
injectedEmbedder().runStoriesAsPaths(storyPaths()) ;
}
}
package foo.bar;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
public class ScenarioContext {
private List<Trade> trades=new ArrayList();
public void addTrade(Trade trade){
trades.add(trade);
}
public ScenarioContext(){
trades = new ArrayList();
}
@PostConstruct
public void resetTrades(){
trades.clear();
}
public Trade getFirstTrade(){
return trades.get(0);
}
}
package foo.bar;
import org.jbehave.core.annotations.BeforeScenario;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ScenarioScope implements Scope {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap();
@BeforeScenario
public void startScenario(){
cache.clear();
}
@Override
public Object get(String name, ObjectFactory objectFactory) throws IllegalStateException{
if (!cache.containsKey(name)){
cache.putIfAbsent(name, objectFactory.getObject());
}
return cache.get(name);
}
@Override
public Object resolveContextualObject(String name){
return null;
}
@Override
public Object remove(String name){
return cache.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback){}
@Override
public String getConversationId(){
return "scenario scope";
}
}
package foo.bar;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import org.jbehave.core.model.ExamplesTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
@Component
public class TradeStep {
@Resource
private TradeService tradeService;
@Autowired
ScenarioContext scenarioContext;
//private List<Trade> trades;
private boolean validationResult;
@Given ("a table: $table")
public void atable(ExamplesTable table){
}
@Given("a trade with a positive price")
public void aTradeWithAPositivePrice() {
scenarioContext.addTrade( new Trade(1));
}
@Given("a trade with a negative price")
public void aTradeWithANegativePrice() {
scenarioContext.addTrade ( new Trade(-1));
}
@When("the trade is validated")
public void theTradeIsValidated() {
validationResult = tradeService.validate(scenarioContext.getFirstTrade());
}
@Then("trade fails validation")
public void tradeFailsValidation() {
assertFalse(validationResult);
}
@Then("trade passes validation")
public void tradePassesValidation() {
assertTrue(validationResult);
}
}
<#ftl strip_whitespace=true>
<#macro renderMultiline text>${text?html?replace("\n", "<br/>")}</#macro>
<#macro renderMeta meta>
<div class="meta">
<div class="keyword">${keywords.meta}</div>
<#assign metaProperties=meta.getProperties()>
<#list metaProperties.keySet() as name>
<#assign property = metaProperties.get(name)>
<div class="property">${keywords.metaProperty}${name?html} ${property?html}</div>
</#list>
</div>
</#macro>
<#macro renderNarrative narrative>
<div class="narrative"><h2>${keywords.narrative}</h2>
<div class="element inOrderTo"><span class="keyword inOrderTo">${keywords.inOrderTo}</span> ${narrative.inOrderTo}</div>
<div class="element asA"><span class="keyword asA">${keywords.asA}</span> ${narrative.asA}</div>
<div class="element iWantTo"><span class="keyword iWantTo">${keywords.iWantTo}</span> ${narrative.iWantTo}</div>
</div>
</#macro>
<#macro renderGivenStories givenStories>
<div class="givenStories">
<div class="keyword">${keywords.givenStories}</div>
<#list givenStories.getStories() as givenStory>
<div class="givenStory">${givenStory.path}</div>
</#list>
</div>
</#macro>
<#macro renderTable table>
<#assign rows=table.getRows()>
<#assign headers=table.getHeaders()>
<table>
<tbody>
<#list headers as header>
<tr>
<#assign cell=rows[0].get(header)>
<td>${header?html}</td>
<td>${cell?html}</td>
</tr>
</#list>
</tbody>
</table>
</#macro>
<#macro renderOutcomes table>
<#assign outcomes=table.getOutcomes()>
<#assign fields=table.getOutcomeFields()>
<table>
<thead><tr>
<#list fields as field>
<th>${field?html}</th>
</#list>
</tr></thead>
<tbody>
<#list outcomes as outcome>
<#assign isVerified=outcome.isVerified()?string>
<#if isVerified == "true"> <#assign verified="verified"><#else><#assign verified="notVerified"></#if>
<tr class="${verified}">
<td>${outcome.description?html}</td><td><@renderOutcomeValue outcome.getValue() table.getDateFormat()/></td><td>${outcome.matcher?html}</td><td><#if isVerified == "true">${keywords.yes}<#else>${keywords.no}</#if></td>
</tr>
</#list>
</tbody>
</table>
</#macro>
<#macro renderOutcomeValue value dateFormat><#if value?is_date>${value?string(dateFormat)}<#else>${value?html}</#if></#macro>
<#macro renderStep step>
<#assign formattedStep = step.getFormattedStep(EscapeMode.HTML, "<span class=\"step parameter\">{0}</span>")>
<div class="step ${step.outcome}">${formattedStep}<#if step.getTable()??> <span class="step parameter"><@renderTable step.getTable()/></span></#if> <@renderStepOutcome step.getOutcome()/></div>
<#if step.getFailure()??><pre class="failure">${step.failureCause?html}</pre></#if>
<#if step.getOutcomes()??>
<div class="outcomes"><@renderOutcomes step.getOutcomes()/>
<#if step.getOutcomesFailureCause()??><pre class="failure">${step.outcomesFailureCause?html}</pre></#if>
</div>
</#if>
</#macro>
<#macro renderStepOutcome outcome><#if outcome=="pending"><span class="keyword ${outcome}">(${keywords.pending})</span></#if><#if outcome=="failed"><span class="keyword ${outcome}">(${keywords.failed})</span></#if><#if outcome=="notPerformed"><span class="keyword ${outcome}">(${keywords.notPerformed})</span></#if></#macro>
<html>
<body>
<div class="story"><h1><@renderMultiline story.getDescription()/></h1>
<div class="path">${story.path}</div>
<#if story.getMeta()??><@renderMeta story.getMeta()/></#if>
<#if story.getNarrative()??><@renderNarrative story.getNarrative()/></#if>
<#assign scenarios = story.getScenarios()>
<#list scenarios as scenario>
<div class="scenario"><h2>${keywords.scenario} <@renderMultiline scenario.getTitle()/></h2>
<#if scenario.getMeta()??><@renderMeta scenario.getMeta()/></#if>
<#if scenario.getGivenStories()??><@renderGivenStories scenario.getGivenStories()/></#if>
<#if scenario.getExamplesTable()??>
<div class="examples"><h3>${keywords.examplesTable}</h3>
<#list scenario.getExamplesSteps() as step>
<div class="step">${step?html}</div>
</#list>
<@renderTable scenario.getExamplesTable()/>
</div> <!-- end examples -->
<#if scenario.getExamples()??>
<#list scenario.getExamples() as example>
<h3 class="example">${keywords.examplesTableRow} ${example?html}</h3>
<#assign steps = scenario.getStepsByExample(example)>
<#list steps as step>
<@renderStep step/>
</#list>
</#list>
</#if>
<#else> <!-- normal scenario steps -->
<#assign steps = scenario.getSteps()>
<#list steps as step>
<@renderStep step/>
</#list>
</#if>
</div> <!-- end scenario -->
</#list>
<#if story.isCancelled()?string == 'true'>
<div class="cancelled">${keywords.storyCancelled} (${keywords.duration} ${story.storyDuration.durationInSecs} s)</div>
</#if>
</div> <!-- end story -->
<#if story.getPendingMethods()??>
<#list story.getPendingMethods() as method>
<div><pre class="pending">${method?html}</pre></div>
</#list>
</#if>
</body>
</html>
Scenario: a trade with a positive price passes validation
Given a trade with a positive price
When the trade is validated
Then trade passes validation
Scenario: a trade with a negative price fails validation
Given a trade with a negative price
When the trade is validated
Then trade fails validation
Scenario: a scenario with a table
Given a table:
|header1 |header2 |header3 |header4 |header5 |header6 |header7 |
|value1 |value2 |value3 |value4 |value5 |value6 |value7 |
Then a table:
|header1 |header2 |header3 |header4 |header5 |header6 |header7 |
|value1 |value2 |value3 |value4 |value5 |value6 |value7 |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="foo.bar"/>
<bean id="scenarioScope" class="foo.bar.ScenarioScope"/>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="scenario" value-ref="scenarioScope"/>
</map>
</property>
</bean>
<bean id="scenarioContext" class="foo.bar.ScenarioContext" scope="scenario">
<aop:scoped-proxy/>
</bean>
</beans>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment