ScalaTest 103: Writing a first BDD test with ScalaTest

Problem: You want to write your ScalaTest tests using a behavior-driven development (BDD) style.

Solution

Extend the ScalaTest FunSpec trait, typically with the BeforeAndAfter trait. Then use the approach shown in the following PizzaSpec test class.

A series of tests begins with the describe method, with individual tests declared in it methods:

package com.acme.pizza

import org.scalatest.FunSpec
import org.scalatest.BeforeAndAfter

class PizzaSpec extends FunSpec with BeforeAndAfter {
  
  var pizza: Pizza = _

  before {
    pizza = new Pizza
  }

  describe("A Pizza") {

    it("should start with no toppings") {
      assert(pizza.getToppings.size == 0)
    }

    it("should allow addition of toppings") (pending)

    it("should allow removal of toppings") (pending)
  }

}

Notice how before, describe, and it seem more like operators than methods. Scala gives developers the power to create their own domain specific languages (DSLs) like this.

Note: Use it in the singular context, and they when referring to a plural context. The methods are interchangeable, and intended to make your code more readable.

This class should be placed in a file named PizzaSpec.scala, in a directory named src/test/scala/com/acme/pizza, under your root SBT folder. Assuming that you also have the Pizza and Topping classes installed as described in ScalaTest 102: Writing TDD tests with ScalaTest, you’ll see the following output when you run the tests with sbt test:

[info] PizzaSpec:
[info] A Pizza 
[info] - should start with no toppings
[info] - should allow addition of toppings (pending)
[info] - should allow removal of toppings (pending)
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 2

Discussion

As you can see, the output of a set of BDD-style tests reads like a software specification: “A Pizza should ...”, followed by a series of statements about what the Pizza class should do. You’ll see how this gets even better in subsequent recipes.

To see what the output looks like when it fails, if you intentionally inject an error into your first test, setting the size to 1 (instead of 0), the sbt test output will look like this instead:

[info] PizzaSpec:
[info] A Pizza 
[info] - should start with no toppings *** FAILED ***
[info]   org.scalatest.exceptions.TestFailedException was thrown.
(PizzaSpec.scala:17)
[info] - should allow addition of toppings (pending)
[info] - should allow removal of toppings (pending)
[error] Failed: : Total 3, Failed 1, Errors 0, Passed 0, Skipped 2
[error] Failed tests:
[error]         com.acme.pizza.PizzaSpec
[error] {file:/Users/Al/Tests/ScalaTest1/}default-d6b943/test:test: 
Tests unsuccessful