Skip to content

Instantly share code, notes, and snippets.

@jonas-grgt
Last active November 21, 2019 13:37
Show Gist options
  • Save jonas-grgt/578cdfb6b4cea4c3e389a50f537b44dd to your computer and use it in GitHub Desktop.
Save jonas-grgt/578cdfb6b4cea4c3e389a50f537b44dd to your computer and use it in GitHub Desktop.
Lightweight BDD based structurizer for Unit Tests
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
@SuppressWarnings("unused")
public class Scenario<T> {
private State state;
private final String description;
private final Supplier<T> contextSupplier;
private List<Step<T>> givenSteps = new ArrayList<>();
private List<Step<T>> whenSteps = new ArrayList<>();
private List<Step<T>> thenSteps = new ArrayList<>();
private Scenario(String description, Supplier<T> contextSupplier) {
this.description = description;
this.contextSupplier = contextSupplier;
}
public Scenario(String description) {
this.description = description;
this.contextSupplier = () -> null;
}
public static <E> Scenario<E> scenario(String description, Supplier<E> contextSupplier) {
return new Scenario<>(description, contextSupplier);
}
public static <E> Scenario<E> scenario(String description) {
return new Scenario<>(description);
}
public Scenario<T> given(String description, ContextConsumer<T> step) {
this.state = State.GIVEN;
this.state.addStepToScenario(this, description, step);
return this;
}
public Scenario<T> given(String description) {
this.state = State.GIVEN;
this.state.addStepToScenario(this, description, c -> {
});
return this;
}
public Scenario<T> and(String description, ContextConsumer<T> step) {
this.state.addStepToScenario(this, description, step);
return this;
}
public Scenario<T> when(String description, Runnable step) {
this.state = State.WHEN;
this.state.addStepToScenario(this, description, step);
return this;
}
public Scenario<T> when(String description, ContextConsumer<T> step) {
this.state = State.WHEN;
this.state.addStepToScenario(this, description, step);
return this;
}
public Scenario<T> then(String description, ContextConsumer<T> step) {
this.state = State.THEN;
this.state.addStepToScenario(this, description, step);
return this;
}
public void verify() {
T context = contextSupplier.get();
givenSteps.forEach(step -> {
try {
step.run(context);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
whenSteps.forEach(step -> {
try {
step.run(context);
} catch (Exception e) {
if(e instanceof RuntimeException) {
throw (RuntimeException)e;
} else {
throw new RuntimeException(e);
}
}
});
thenSteps.forEach(step -> {
try {
step.run(context);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
public static interface Step<T> {
void run(T context) throws Exception;
static <T> Step<T> valueOf(String description, ContextConsumer<T> step) {
return new ContextAwareStep<>(description, step);
}
static <T> Step<T> valueOf(String description, Runnable step) {
return new ContextLessStep<>(description, step);
}
}
public static class ContextAwareStep<T> implements Step<T> {
private final String description;
private final ContextConsumer<T> step;
public ContextAwareStep(String description, ContextConsumer<T> step) {
this.description = description;
this.step = step;
}
public static <T> ContextAwareStep<T> valueOf(String description, ContextConsumer<T> runner) {
return new ContextAwareStep<>(description, runner);
}
public void run(T context) throws Exception {
step.accept(context);
}
}
public static class ContextLessStep<T> implements Step<T> {
private final String description;
private final Runnable step;
public ContextLessStep(String description, Runnable step) {
this.description = description;
this.step = step;
}
public static <T> ContextLessStep<T> valueOf(String description, Runnable runner) {
return new ContextLessStep<>(description, runner);
}
public void run(T context) {
step.run();
}
}
private enum State {
GIVEN {
@Override
<T> void addStepToScenario(Scenario<T> scenario, String description, ContextConsumer<T> step) {
scenario.givenSteps.add(Step.valueOf(description, step));
}
@Override
<T> void addStepToScenario(Scenario<T> scenario, String description, Runnable step) {
scenario.givenSteps.add(Step.valueOf(description, step));
}
}, WHEN {
@Override
<T> void addStepToScenario(Scenario<T> scenario, String description, ContextConsumer<T> step) {
scenario.whenSteps.add(Step.valueOf(description, step));
}
@Override
<T> void addStepToScenario(Scenario<T> scenario, String description, Runnable step) {
scenario.whenSteps.add(Step.valueOf(description, step));
}
}, THEN {
@Override
<T> void addStepToScenario(Scenario<T> scenario, String description, ContextConsumer<T> step) {
scenario.thenSteps.add(Step.valueOf(description, step));
}
@Override
<T> void addStepToScenario(Scenario<T> scenario, String description, Runnable step) {
scenario.thenSteps.add(Step.valueOf(description, step));
}
};
abstract <T> void addStepToScenario(Scenario<T> scenario, String description, ContextConsumer<T> step);
abstract <T> void addStepToScenario(Scenario<T> scenario, String description, Runnable step);
}
@FunctionalInterface
public interface ContextConsumer<T> {
void accept(T t) throws Exception;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment