FitNesse your ScalaTest with custom Scala DSL
DoFixture
. DoFixture
in FitNesse allows one to write very readable acceptance tests almost in plain English using Wiki pages:!|CarRegistrationFixtureTest|What might not be obvious is that the last line is actually executable and calls good old Java (or Scala for that matter) method:
!1 Registering car
!2 Registering brand new car for the first time
| register | brand new car | by | any owner | in | any country |
class CarRegistrationFixtureTest extends DoFixture {Notice how oddly named
val carService = new CarService
def registerByIn(car: Car, owner: Owner, where: Country) = {
//...
}
}
registerByIn
method maps to " register brand new car by any owner in any country" wiki syntax. What we will learn today is writing very simple, custom Scala DSL that is even more readable and does not require new tool and testing framework.The same test written in ScalaTest would look something like this:
class CarRegistrationSpec extends FeatureSpec with GivenWhenThen {Nothing fancy, ordinary
val carService = new CarService()
feature("Registering car") {
scenario("Registering brand new car for the first time") {
Given("Owner and brand new car")
//...
When("Car registered")
carService.registerCar(brandNewCar, anyOwner, anyCountry)
//...
}
}
}
registerCar()
method call. The rest of the test (as well as the declaration of brandNewCar
, anyOwner
and anyCountry
) is not relevant to our discussion. We can make it a little bit more readable by explicitly naming parameters:carService.registerCar(car = brandNewCar, owner = anyOwner, where = anyCountry)However it's not clear whether this is actually more readable for e.g. non-programmers. But since we already use descriptive
FeatureSpec
, can we make Scala code a little bit more human eye-friendly? Of course! Our biggest friends are infix notation and fluent API pattern:def register(car: Car) = new {Looks weird, but put this code in your test and enjoy much more fluent call:
def by(owner: Owner) = new {
def in(country: Country) =
carService.registerCar(car, owner, country)
}
}
register(brandNewCar).by(anyOwner).in(anyCountry)This is as far as Java can go, but Scala has infix method call syntax, which is equivalent and looks beautiful:
register(brandNewCar) by anyOwner in anyCountryWhy couldn't we skip first parentheses? It is a limitation on where infix notation can be used (only one-argument method calls on an object). Fortunately we can easily refactor our internal testing DSL by pushing noun (owner) onto first place and using implicit conversion:
implicit def fluentCarRegister(owner: Owner) = new {...which can be used as follows:
def registers(car: Car) = new {
def in(country: Country) =
carService.registerCar(car, owner, country)
}
}
anyOwner registers brandNewCar in anyCountryIf you got lost, the line of code above is still Scala and is still executable. If you don't quite get what's happening, here is a desugared syntax:
fluentCarRegister(anyOwner).registers(brandNewCar).in(anyCountry)Tests are all about readability and maintainability. Many people are scared of DSLs because often they are hard to implement and debug. As I showed in this short article, writing really simple yet impressive test DSL in Scala is both simple and rewarding. Moreover there is no reflection or magic meta-programming. Tags: dsl, scala, scalatest, testing