A collection of ScalaTest BDD examples using FunSpec

This page is very much a work in progress, but it currently shows a small collection of ScalaTest BDD examples. I’ll keep adding more BDD examples as time goes on. Also, in this article I assume that you are familiar/comfortable with Scala and SBT, and are at least slightly familiar with using ScalaTest.

Back to top

Getting started

Use the FunSpec ScalaTest class, generally with describe, and assert tests in it and they methods. There are many FunSpec examples at doc.scalatest.org/2.1.0/#org.scalatest.FunSpec.

My SBT build.sbt file from a recent project used this configuration:

name := "ScalaApacheAccessLogParser"

version := "1.0"

scalaVersion := "2.10.0"

scalacOptions += "-deprecation"

libraryDependencies += "org.scalatest" % "scalatest_2.10" % "2.1.0" % "test"
Back to top

First steps

To get started with a simple BDD test, here’s a simple example from that documentation page:

describe("The combinators") {
    they("should be easy to learn") {}
    they("should be efficient") {}
    they("should do something cool") {}
}

This next example shows a series of BDD tests. Each one uses 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)
    }

}
Back to top

Adding Given/When/Then behavior (and ‘And’)

Mix the GivenWhenThen trait into your FunSpec BDD test, then add the Given/When/Then conditions, as shown in the following example code:

package com.acme.pizza

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

class PizzaSpec extends FunSpec with GivenWhenThen {

    var pizza: Pizza = _

    describe("A Pizza") {

      it ("should allow the addition of toppings") {

        Given("a new pizza")
        pizza = new Pizza

        When("a topping is added")
        pizza.addTopping(Topping("green olives"))

        Then("the topping count should be incremented")
        expectResult(1) {
            pizza.getToppings.size
        }

        And("the topping should be what was added")
        val t = pizza.getToppings(0)
        assert(t === new Topping("green olives"))

    }
  }
}
Back to top

More on Given, When, Then, and And

Here’s a larger Given, When, Then, and And example:

describe("Testing the first access log record") {

  it("the data fields should be correct") {
      Given("the first sample log record")
      records = SampleCombinedAccessLogRecords.data
      val parser = new ApacheCombinedAccessLogParser
      val rec = parser.parseRecord(records(0))

      Then("parsing record(0) should not return None")
          assert(rec != None)

      And("the ip address should be correct")
          assert(rec.get.clientIpAddress == "124.30.9.161")

      And("client identity")
          assert(rec.get.rfc1413ClientIdentity == "-")

      And("remote user")
          assert(rec.get.remoteUser == "-")

      And("date/time")
          assert(rec.get.dateTime == "[21/Jul/2009:02:48:11 -0700]")

      And("request")
          assert(rec.get.request == "GET /java/edu/pj/pj010004/pj010004.shtml HTTP/1.1")

      And("status code should be 200")
          assert(rec.get.serverStatusCode == "200")

      And("bytes sent should be 16731")
          assert(rec.get.bytesSent == "16731")

      And("referer")
          assert(rec.get.referer == "http://www.google.co.in/search?q=reading+data+from+file+in+java")

      And("user agent")
          assert(rec.get.userAgent == "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 GTB5")

    }
}
Back to top

Add more tests within ‘describe’

Keep adding more it tests within the describe block:

// this code should be in a 'describe' block

it("Should start with no toppings") {

    Given("a new pizza")
    pizza = new Pizza

    Then("the topping count should be zero")
    assert(pizza.getToppings.size == 0)

}

it("Should allow removal of toppings") {

    Given("a new pizza with one topping")
    pizza = new Pizza
    pizza.addTopping(Topping("green olives"))

    When("the topping is removed")
    pizza.removeTopping(Topping("green olives"))

    Then("the topping count should be zero")
    expectResult(0) {
        pizza.getToppings.size
    }

}
Back to top

Testing Option/Some/None in a BDD test

Here’s an example of how to test a method when an Option (Option, Some[A], or None) is returned from that method:

describe("Parsing the date field ...") {

    it("a valid date field should work") {
        val date = ApacheCombinedAccessLogParser.parseDateField("[21/Jul/2009:02:48:13 -0700]")
        assert(date != None)
        date.foreach { d =>
            val cal = Calendar.getInstance
            cal.setTimeInMillis(d.getTime)
            assert(cal.get(Calendar.YEAR) == 2009)
            assert(cal.get(Calendar.MONTH) == 6)  // 0-based
            assert(cal.get(Calendar.DAY_OF_MONTH) == 21)
            assert(cal.get(Calendar.HOUR) == 2)
            assert(cal.get(Calendar.MINUTE) == 48)
            assert(cal.get(Calendar.SECOND) == 13)
        }
    }

    it("an invalid date field should return None") {
        val date = ApacheCombinedAccessLogParser.parseDateField("[foo bar]")
        assert(date == None)
    }

}
Back to top

Nesting describe blocks

You can nest describe blocks within other describe blocks. The following example is from the ScalaTest FunSpec page:

import org.scalatest.FunSpec

class SetSpec extends FunSpec {

    describe("A Set") {
        describe("when empty") {
            it("should have size 0") {
                assert(Set.empty.size == 0)
            }

            it("should produce NoSuchElementException when head is invoked") {
                intercept[NoSuchElementException] {
                Set.empty.head
            }
        }
    }
}
Back to top

Using ‘before’ and ‘after’

In theory, you should be able to use before like this:

var records: Seq[String] = _

before {
    records = SampleCombinedAccessLogRecords.data
}

However, I haven’t been able to get this to work, and I get a NullPointerException when I try to access records. (I need to look into this.)

Back to top

Mark tests as pending

You can mark it and they tests as “pending”:

it("should allow removal of toppings") (pending)
Back to top

Temporarily disabling tests

You can temporarily disable ScalaTest tests by changing it or they to ignore:

ignore ("A new pizza has zero toppings", DatabaseTest) {

    Given("a new pizza")
    pizza = new Pizza

    Then("the topping count should be zero")
    assert(pizza.getToppings.size == 0)

}
Back to top

Testing expected exceptions

Use the intercept method to verify the exception occurs. In the following example, the boom method will always throw an exception. The intercept method lets you catch that exception, so you can verify that this portion of the method works as desired:

test ("catching an exception") {

    val thrown = intercept[Exception] {
        pizza.boom
    }
    assert(thrown.getMessage === "Boom!")

}

Here’s how this code works:

  • If boom throws an exception, intercept will catch and return that exception. This lets you assign it to a variable like thrown.
  • If boom completes normally, or throws an exception you weren’t expecting, intercept will complete abruptly with a TestFailedException.

As shown, you catch the exception in a value named something like thrown, and then test the message from the exception inside an assert method.

This example used intercept to catch the exception, and assert to test the exception message, but this isn’t the only possible solution. The following code shows that you don’t have to catch the exception as an object and then test its message. Because intercept will end the test with a TestFailedException if your code doesn’t throw an exception, your test can be as simple as this:

test ("catching an exception") {
    intercept[Exception] { pizza.boom }
}

If your code throws the exception, intercept catches it, and the test succeeds. (You expected it to throw an exception, and it did.)

Conversely, if your method doesn’t throw an exception, intercept will make the test fail. The output looks like this:

[info] - catching an exception *** FAILED ***

[info]   Expected exception java.lang.Exception to be thrown,
         but no exception was thrown. (Test.scala:27)

Of course, you can also use a try/catch block to test that the exception occurs under the right situations, but intercept was created as a way to assist with this common testing pattern.

Back to top

Assertions

The previous examples showed how to use assertions. I’ll add more about them here in the future, but for now, this link has good info: http://scalatest.org/user_guide/using_assertions

Back to top

Using matchers

I’ll write add some matcher examples here in the future, but until then, here are some details: http://scalatest.org/user_guide/using_matchers.

Back to top

Tagging your BDD tests

Create one or more custom tags, then include those tags in your BDD test specifications. When you run your tests, declare which tests you want to run, or not run.

First, define a Tag like this:

package com.acme.pizza

import org.scalatest.Tag

object DatabaseTest extends Tag("DatabaseTest")

This defines a Tag named DatabaseTest that you can use in your tests.

Next, add the tag to your database tests. This is how you add a tag to an it BDD-style test:

// add the 'DatabaseTest' tag to the 'it' method

it("Should start with no toppings", DatabaseTest) {

    Given("a new pizza")
    pizza = new Pizza

    Then("the topping count should be zero")
    assert(pizza.getToppings.size == 0)

}

Now when you run your tests, you can specify which tests you want to include. To include tests with the DatabaseTest tag in the list of tests to be run, start SBT, and then issue the following test-only command from within the SBT shell:

$ sbt

sbt> test-only -- -n DatabaseTest

To exclude tests with the DatabaseTest tag from being run, use this approach:

sbt> test-only -- -l DatabaseTest

(The second example uses a lowercase letter “L”.)

Notice that this uses a different version of the it method than was used here. This example calls the two-argument version of the it method, whereas previous examples used the one-argument version.

To add multiple tags to one BDD-style it test, add them as additional parameters:

it("Should start with no toppings", DatabaseTest, WsTest) {
    // test code here
}

There are additional ways to tag tests. For instance, the ScalaTest documentation shows how to use taggedAs with an it test in a class that extends FlatSpec:

it must "subtract correctly" taggedAs(SlowTest, DbTest) in {
    val diff = 4 - 1
    assert(diff === 3)
}

To run tests that have both tags DatabaseTest and WsTest, separate their names by blank spaces inside quotes with the -n flag:

sbt> test-only -- -n "DatabaseTest WsTest"

Or to exclude those tests, do the same thing with the -l (lowercase ’L’) flag:

sbt> test-only -- -l "DatabaseTest WsTest"

More information on ScalaTest tags:

Back to top

Other

There are a few more things to know about BDD tests in ScalaTest, and I’ll add those here after I have used them (or used them more):

  • info
  • markup
  • alert
  • note
  • before and after (I’ve had a weird problem getting these two work correctly, and need to look into it)
Back to top

More information

More information on BDD testing and ScalaTest:

If you wanted to see a collection of ScalaTest BDD tests all on one page, I hope this has been helpful.

Back to top

Share it!

There’s just one person behind this website; if this article was helpful (or interesting), I’d appreciate it if you’d share it. Thanks, Al.

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.