Specsy is a BDD-style unit-level testing framework for Scala, Groovy, Java and easily any other language on the JVM. It safely isolates mutable state, supports writing self-documenting tests/specifications, and runs all tests in parallel.

Specsy has all the essential features of a unit testing framework and nothing excess. To illustrate Specsy’s expressiveness, its public API has only three methods, but they provide functionality that requires about four printed pages of documentation to cover - more than Specsy even has production code.

The following example demonstrates Specsy’s isolated execution model.

class StackSpec extends ScalaSpecsy {
  val stack = new scala.collection.mutable.Stack[String]

  "An empty stack" >> {

    "is empty" >> {
      assertTrue(stack.isEmpty)
    }
    "After a push, the stack is no longer empty" >> {
      stack.push("a push")
      assertFalse(stack.isEmpty)
    }
  }

  "When objects have been pushed onto a stack" >> {
    stack.push("pushed first")
    stack.push("pushed last")

    "the object pushed last is popped first" >> {
      val poppedFirst = stack.pop()
      assertThat(poppedFirst, is("pushed last"))
    }
    "the object pushed first is popped last" >> {
      stack.pop()
      val poppedLast = stack.pop()
      assertThat(poppedLast, is("pushed first"))
    }
    "After popping all objects, the stack is empty" >> {
      stack.pop()
      stack.pop()
      assertTrue(stack.isEmpty)
    }
  }
}
class StackSpec extends GroovySpecsy {
    def stack = new ArrayDeque<String>()

    @Override
    void run() {

        spec "An empty stack", {

            spec "is empty", {
                assertTrue(stack.isEmpty())
            }
            spec "After a push, the stack is no longer empty", {
                stack.push("a push")
                assertFalse(stack.isEmpty())
            }
        }

        spec "When objects have been pushed onto a stack", {
            stack.push("pushed first")
            stack.push("pushed last")

            spec "the object pushed last is popped first", {
                String poppedFirst = stack.pop()
                assertThat(poppedFirst, is("pushed last"))
            }
            spec "the object pushed first is popped last", {
                stack.pop()
                String poppedLast = stack.pop()
                assertThat(poppedLast, is("pushed first"))
            }
            spec "After popping all objects, the stack is empty", {
                stack.pop()
                stack.pop()
                assertTrue(stack.isEmpty())
            }
        }
    }
}
public class StackSpec extends JavaSpecsy {
    private Deque<String> stack = new ArrayDeque<>();

    @Override
    public void run() {

        spec("An empty stack", () -> {

            spec("is empty", () -> {
                assertTrue(stack.isEmpty());
            });
            spec("After a push, the stack is no longer empty", () -> {
                stack.push("a push");
                assertFalse(stack.isEmpty());
            });
        });

        spec("When objects have been pushed onto a stack", () -> {
            stack.push("pushed first");
            stack.push("pushed last");

            spec("the object pushed last is popped first", () -> {
                String poppedFirst = stack.pop();
                assertThat(poppedFirst, is("pushed last"));
            });
            spec("the object pushed first is popped last", () -> {
                stack.pop();
                String poppedLast = stack.pop();
                assertThat(poppedLast, is("pushed first"));
            });
            spec("After popping all objects, the stack is empty", () -> {
                stack.pop();
                stack.pop();
                assertTrue(stack.isEmpty());
            });
        });
    }
}

Refer to the documentation to see more examples and to start using Specsy.

Project Goals

  • Unlimited Nesting - The specs can be organized into a nested hierarchy. This makes it possible to apply One Assertion Per Test which isolates the reason for a failure, because the specs are very fine-grained. This flexibility also makes writing specification-style tests easier.

  • Isolated Execution - To make it easy to write repeatable tests, each spec is isolated from the side-effects of its sibling specs. By default, each spec will see only the side-effects of its parent specs. Note that Specsy discourages writing non-repeatable fat integration tests, so a BeforeClass/AfterClass concept is outside the scope of this project (or at a very low priority - I have some ideas on how it could be done elegantly).

  • No Forced Words - In order to let you choose the best possible test names, Specsy does not impose any predefined words on its users.

  • Simplicity - Specsy contains only the essential features, but does them well. Having a particular assertion syntax is not essential and it’s easy to use the assertions of other testing libraries, so Specsy itself does not have assertions. Also any syntactic sugar is minimized, in order for it to be easy to know what the code does just by looking at it.

  • Parallel Execution - Running tests fast is a must for using TDD (my pain threshold for recompile and test execution is about 5-10 seconds). With Specsy all tests in a test class can be run in parallel, taking full use of all available CPU cores.