tw.teddysoft.ezspec.keyword.Scenario Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ezspec-core Show documentation
Show all versions of ezspec-core Show documentation
ezspec is a framework for adopting Gherkin in Java testing.
The newest version!
package tw.teddysoft.ezspec.keyword;
import org.junit.jupiter.api.DynamicNode;
import tw.teddysoft.ezspec.exception.EzSpecError;
import tw.teddysoft.ezspec.extension.junit5.*;
import tw.teddysoft.ezspec.keyword.table.Table;
import tw.teddysoft.ezspec.keyword.visitor.SpecificationElement;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;
import static java.lang.String.format;
import static tw.teddysoft.ezspec.extension.SpecUtils.getReplacedUnderscores;
/**
* {@code Scenario} is a class for representing Gherkin scenario.
*
* @author Teddy Chen
* @since 1.0
*/
public abstract class Scenario implements SpecificationElement {
public static final String KEYWORD = "Scenario";
protected final String name;
protected final List steps;
protected Table lookupTable;
protected ScenarioEnvironment runtime;
protected int index;
protected Rule rule;
public static final String ARG = "$ARGUMENT";
/**
* Gets scenario environment of this scenario.
*
* @return the scenario environment
*/
protected ScenarioEnvironment getEnvironment(){
return runtime;
}
/**
* Instantiates a new Scenario.
*
* @param name the name
*/
public Scenario(String name, Rule rule){
this(name, new Table(), rule);
}
/**
* Instantiates a new Scenario.
*
* @param name the name of this scenario
* @param background the background of this scenario
*/
public Scenario(String name, Background background, Rule rule){
Objects.requireNonNull(name, "name");
Objects.requireNonNull(background, "background");
this.name = name;
steps = new ArrayList<>();
this.runtime = ScenarioEnvironment.clone(background.getEnvironment());
this.lookupTable = background.activeTable();
this.rule = rule;
}
/**
* Instantiates a new Scenario.
*
* @param name the name of this scenario
* @param table the table of this scenario
*/
public Scenario(String name, Table table, Rule rule){
Objects.requireNonNull(name, "name");
Objects.requireNonNull(table, "table");
Objects.requireNonNull(rule, "rule");
this.name = name;
steps = new ArrayList<>();
this.runtime = ScenarioEnvironment.create();
this.runtime.setInput(table);
lookupTable = table;
this.rule = rule;
}
public Scenario withRule(String ruleName){
Objects.requireNonNull(ruleName);
var withRule = getFeature().getRule(ruleName);
if (withRule.isEmpty())
throw new IllegalArgumentException("Rule not found: " + ruleName);
getFeature().applyRule(ruleName, this);
this.runtime = ScenarioEnvironment.clone(withRule.get().getBackground().getEnvironment());
this.rule = withRule.get();
return this;
}
public Scenario withRule(Rule rule){
Objects.requireNonNull(rule);
return withRule(rule.getName());
}
private Feature getFeature(){
return rule.getFeature();
}
/**
* Gets active table of this scenario.
*
* @return the table
*/
public Table activeTable(){
return lookupTable;
}
public String getName() {
return name;
}
/**
* The method for representing Gherkin Given step of this scenario.
*
* @param description the description of Given step
* @param continuous the parameter for deciding to continue executing
* the next step after this Given step failed
* @param callback a lambda expression to represent the implementation
* of this Given step
* @return this scenario
*/
//region :Keywords
public abstract Scenario Given(String description, boolean continuous, Consumer callback);
/**
* The method for representing Gherkin Given step of this scenario.
*
* @param description the description of Given step
* @param callback a lambda expression to represent the implementation
* of this Given step
* @return this scenario
*/
public abstract Scenario Given(String description, Consumer callback);
/**
* The method for representing Gherkin When step of this scenario.
*
* @param description the description of When step
* @param continuous the parameter for deciding to continue executing
* the next step after this When step failed
* @param callback a lambda expression to represent the implementation
* of this When step
* @return this scenario
*/
public abstract Scenario When(String description, boolean continuous, Consumer callback);
/**
* The method for representing Gherkin When step of this scenario.
*
* @param description the description of When step
* @param callback a lambda expression to represent the implementation
* of this When step
* @return this scenario
*/
public abstract Scenario When(String description, Consumer callback);
/**
* The method for representing Gherkin Then step of this scenario.
*
* @param description the description of Then step
* @param continuous the parameter for deciding to continue executing
* the next step after this Then step failed
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario Then(String description, boolean continuous, Consumer callback);
/**
* The method for representing Gherkin Then step of this scenario.
*
* @param description the description of Then step
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario Then(String description, Consumer callback);
/**
* The method for representing Gherkin Scenario execution succeeded before
* asserting following Then steps.
*
* @param continuous the parameter for deciding to continue executing
* the next step after this Then step failed
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenSuccess(boolean continuous, Consumer callback);
/**
* The method for representing Gherkin Scenario execution succeeded before
* asserting following Then steps.
*
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenSuccess(Consumer callback);
/**
* The method for representing Gherkin Scenario execution succeeded before
* asserting following Then steps.
*
* @param description the description of ThenSuccess step
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenSuccess(String description, Consumer callback);
/**
* The method for representing Gherkin Scenario execution succeeded before
* asserting following Then steps.
*
* @param description the description of ThenSuccess step
* @param continuous the parameter for deciding to continue executing
* the next step after this Then step failed
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenSuccess(String description, boolean continuous, Consumer callback);
/**
* The method for representing Gherkin Scenario execution failed before
* asserting following Then steps.
*
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenFailure(Consumer callback);
/**
* The method for representing Gherkin Scenario execution failed before
* asserting following Then steps.
*
* @param continuous the parameter for deciding to continue executing
* the next step after this Then step failed
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenFailure(boolean continuous, Consumer callback);
/**
* The method for representing Gherkin Scenario execution failed before
* asserting following Then steps.
*
* @param description the description of ThenFailure step
* @param continuous the parameter for deciding to continue executing
* the next step after this Then step failed
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenFailure(String description, boolean continuous, Consumer callback);
/**
* The method for representing Gherkin Scenario execution failed before
* asserting following Then steps.
*
* @param description the description of ThenFailure step
* @param callback a lambda expression to represent the implementation
* of this Then step
* @return this scenario
*/
public abstract Scenario ThenFailure(String description, Consumer callback);
/**
* The method for representing Gherkin And step.
*
* @param description the description of And step
* @param callback a lambda expression to represent the implementation
* of this And step
* @return this scenario
*/
public abstract Scenario And(String description, Consumer callback);
/**
* The method for representing Gherkin And step.
*
* @param description the description of And step
* @param continuous the parameter for deciding to continue executing
* the next step after this And step failed
* @param callback a lambda expression to represent the implementation
* of this And step
* @return this scenario
*/
public abstract Scenario And(String description, boolean continuous, Consumer callback);
/**
* The method for representing Gherkin But step.
*
* @param description the description of But step
* @param callback a lambda expression to represent the implementation
* of this But step
* @return this scenario
*/
public abstract Scenario But(String description, Consumer callback);
/**
* The method for representing Gherkin But step.
*
* @param description the description of But step
* @param continuous the parameter for deciding to continue executing
* the next step after this But step failed
* @param callback a lambda expression to represent the implementation
* of this But step
* @return this scenario
*/
public abstract Scenario But(String description, boolean continuous, Consumer callback);
//endregion :Keywords
/**
* A static method for formatting the name.
*
* @param originName the origin name
* @return the string
*/
protected static String replaceName(String originName){
return originName.replace("_", " ")
.replace("$dot$", ".")
.replace("$parenthesis$", "()")
.replace("$comma$", ",");
}
public String getDisplayName(){
return replaceName(getName());
}
/**
* Gets test case enclosing name.
*
* @return the test case enclosing name
*/
public static String getEnclosingMethodName() {
StackWalker walker = StackWalker.getInstance();
Optional methodName = walker.walk(frames -> frames
.skip(2)
.findFirst()
.map(StackWalker.StackFrame::getMethodName));
var cookedMethodName = methodName.get();
if (cookedMethodName.startsWith("lambda$")){
cookedMethodName = cookedMethodName.replace("lambda$", "");
cookedMethodName = cookedMethodName.substring(0, cookedMethodName.lastIndexOf("$"));
}
return cookedMethodName;
}
public static MethodInfo getEnclosingMethodInfo() {
StackWalker walker = StackWalker.getInstance();
Optional methodName = walker.walk(frames -> frames
.skip(2)
.findFirst()
.map(StackWalker.StackFrame::getMethodName));
var className = walker.walk(frames -> frames
.skip(2)
.findFirst()
.map(StackWalker.StackFrame::getClassName));
var cookedMethodName = methodName.get();
if (cookedMethodName.startsWith("lambda$")){
cookedMethodName = cookedMethodName.replace("lambda$", "");
cookedMethodName = cookedMethodName.substring(0, cookedMethodName.lastIndexOf("$"));
}
MethodInfo methodInfo = new MethodInfo(methodName.get(), cookedMethodName, getRuleName(className.get(), methodName.get()));
return methodInfo;
}
private static String getRuleName(String classNAme, String methodName) {
Method method;
Class cls;
try{
cls = Class.forName(classNAme);
method = cls.getMethod(methodName);
method.setAccessible(true);
}
catch (NoSuchMethodException | ClassNotFoundException e){
throw new RuntimeException(e.getMessage(), e);
}
Annotation[] methodAnnotations = method.getDeclaredAnnotations();
for (Annotation annotation : methodAnnotations) {
switch (annotation){
case EzScenario e when !e.rule().isEmpty() -> {return e.rule();}
case EzScenarioOutline e when !e.rule().isEmpty() -> {return e.rule();}
case EzDynamicScenario e when !e.rule().isEmpty() -> {return e.rule();}
case EzDynamicScenarioOutline e when !e.rule().isEmpty() -> {return e.rule();}
default -> {}
}
}
Annotation[] classAnnotations = cls.getDeclaredAnnotations();
for (Annotation annotation : classAnnotations) {
if (annotation instanceof EzRule ezRule) {
return ezRule.value();
}
}
return "";
}
/**
* Gets steps in this scenario.
*
* @return the steps
*/
public List steps(){
return Collections.unmodifiableList(steps);
}
/**
* Gets steps in this scenario.
*
* @return the steps
*/
public List getSteps() {
return steps;
}
/**
* Gets the scenario name with underscores replaced by space.
*
* @return the replaced name
*/
public String getReplacedUnderscoresName(){
return getReplacedUnderscores(getName());
}
/**
* Execute the Given/When/Then step and
* its following And/But steps concurrently.
*/
public abstract void ExecuteConcurrently();
/**
* Execute scenario with Junit{@code DynamicNode}.
*
* @return the dynamic node
*/
public abstract DynamicNode DynamicExecute();
/**
* Execute steps in Junit{@code DynamicNode}.
*
* @param step the step
* @throws Throwable the throwable when step failed
*/
protected void dynamicExecuteStep(Step step) throws Throwable {
if (step.getResult().isSuccess()) return;
switch (step.getResult().getExecutionOutcome()) {
case Pending : return;
case Success : return;
case Failure : throw step.getResult().getException();
case Skipped : return;
default : {}
}
}
/**
* Execute this scenario.
*/
public abstract void Execute();
/**
* Builds spec errors as an {@code EzSpecError}.
*
* @param steps the steps
* @return the {@code EzSpecError}
*/
protected Optional buildSpecError(List steps){
List throwables = new ArrayList<>();
StringBuilder sb = new StringBuilder();
int errorCount = 0;
for(var step : steps){
if (step.getResult().isFailure()) {
errorCount = errorCount + 1;
throwables.add(step.getResult().getException());
var failureMessage = step.getResult().getFailureMessage();
sb.append(format("\n[%d] %s", errorCount, failureMessage));
}
}
if (throwables.isEmpty()){
return Optional.empty();
}
return Optional.of(new EzSpecError(sb.toString()));
}
public record MethodInfo(
String methodName,
String cookedMethodName,
String ruleName
){}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy