jdoc-spock-commons/src/main/java/org/bool/jdoc/spock/SpockSpecGenerator.java
package org.bool.jdoc.spock;
import com.github.javaparser.ast.CompilationUnit;
import lombok.AllArgsConstructor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;
import static java.util.stream.Collectors.joining;
@AllArgsConstructor
public class SpockSpecGenerator {
private static final String LF = "\n";
private final ClassIntrospector classIntrospector;
private final Function<Class<?>, String> classNameFormat;
public static String defaultNameFormat(Class<?> cls) {
return cls.getSimpleName() + "TestSpec";
}
public SpockSpecGenerator() {
this(new ClassIntrospector(), SpockSpecGenerator::defaultNameFormat);
}
/**
* Generate test spec for targetClass using code in codeBlocks.
*
* <pre><code lang="spock">
* def "Spec generated for targetClass"() {
* given:
* classIntrospector.findMockConstructor(SpockSpecGenerator.class) >> Optional.of(SpockSpecGenerator.class.getConstructor())
* classNameFormat.apply(SpockSpecGenerator.class) >> SpockSpecGenerator.defaultNameFormat(SpockSpecGenerator.class)
* when:
* def spec = $target.generateSpec(new CompilationUnit("org.bool.jdoc"), ['def "test method"() { }'], SpockSpecGenerator.class)
* then:
* with (spec) {
* type == "spock"
* name == "SpockSpecGeneratorTestSpec"
* script.startsWith("package org.bool.jdoc")
* script.contains("class $name extends Specification")
* script.contains('def $target = new SpockSpecGenerator()')
* script.contains('def "test method"() { }')
* }
* }
* </code></pre>
*/
public TestSpec generateSpec(CompilationUnit unit, List<String> codeBlocks, Class<?> targetClass) {
return generateSpec(unit, codeBlocks, targetClass, classIntrospector.findMockConstructor(targetClass).orElse(null));
}
public TestSpec generateSpec(CompilationUnit unit, List<String> codeBlocks, Class<?> targetClass, Constructor<?> ctor) {
String name = classNameFormat.apply(targetClass);
return new TestSpec("spock", name, generateSpec(name, unit, codeBlocks, ctor));
}
private String generateSpec(String name, CompilationUnit unit, List<String> codeBlocks, Constructor<?> ctor) {
Builder<String> spec = Stream.builder();
unit.getPackageDeclaration().map(Object::toString).ifPresent(spec);
spec.add("import spock.lang.*").add(LF);
unit.getImports().stream().map(Object::toString).forEach(spec);
spec.add(String.format("class %s extends Specification {", name));
defMockFields(ctor).forEach(spec);
defTargetField(ctor).ifPresent(spec);
codeBlocks.forEach(spec);
return spec.add("}").build().collect(joining(LF));
}
private Stream<String> defMockFields(Constructor<?> ctor) {
return IntStream.range(0, ctor != null ? ctor.getParameterCount() : 0)
.mapToObj(index -> String.format("def %s = Mock(%s)", ctor.getParameters()[index].getName(), ctor.getParameterTypes()[index].getSimpleName()));
}
private Optional<String> defTargetField(Constructor<?> constructor) {
return Optional.ofNullable(constructor)
.map(ctor -> String.format("def $target = new %s(%s)",
ctor.getDeclaringClass().getSimpleName(),
Stream.of(ctor.getParameters()).map(Parameter::getName).collect(joining(", "))));
}
}