Dependency matchers

It is highly desirable to have acyclic dependencies between the packages of a project. This often leads to describing the packages structure as “layered”: each package on a layer can only depend on a package on a lower layer. specs2 helps you enforce this design property with specific matchers.

Layers definition

First you need to define the packages and their expected dependencies. Mix-in the org.specs2.specification.Analysis trait and define layers (taking specs2 as an example):

layers (
  "runner",
  "reporter",
  "specification mutable",
  "mock      form",
  "matcher",
  "execute",
  "reflect    xml  time html",
  "collection control io text main data").withPrefix("org.specs2")

The above expression defines layers as an ordered list of Strings containing space-separated package names. It is supplemented by a withPrefix declaration to factor out the common package prefix between all these packages.

By default, the packages are supposed to correspond to directories in the src/target/scala-<version>/classes directory. If your project has a different layout you can declare another target directory:

layers("...").inTargetDir("out" / "classes")

Inclusion/Exclusion

Every rule has exceptions :-). In some rare cases, it might be desirable to exclude a class from being checked on a given layer. To do this, you can use the include/exclude methods on the Layer class:

layers (
  "runner",
  "reporter",
  "specification mutable".exclude("mutable.SpecificationWithJUnit"),
  "mock      form",
  "matcher",
  "execute",
  "reflect  xml  time html",
  "collection control io text main data").withPrefix("org.specs2")

The include/exclude methods accept a list of regular expressions to:

Verification

Now you’ve defined layers, you can use the beRespected matcher to check if all the dependencies are verified:

val design = layers("...")
design must beRespected

If some dependencies are not respected:

[error] those dependencies are not satisfied:
[error] org.specs2.main x-> org.specs2.io because org.specs2.io.FileSystem -> org.specs2.main.Arguments
[error] org.specs2.main x-> org.specs2.io because org.specs2.io.FileSystem -> org.specs2.main.ArgumentsArgs

Layers as an Example

The org.specs2.specification.Analysis trait allows to directly embed the layers definition in a Specification and turn it into an Example:

class DependenciesSpec extends Specification with specification.Analysis { def is =
  "this is the application design" ^
    layers(
      "gui commandline",
      "controller",
      "backend"
    )
}