Table of Contents
- Getting started
- First steps
- Adding Given/When/Then behavior (and ‘And’)
- More on Given, When, Then, and And
- Add more tests within ‘describe’
- Testing Option/Some/None in a BDD test
- Nesting describe blocks
- Using ‘before’ and ‘after’
- Mark tests as pending
- Temporarily disabling tests
- Testing expected exceptions
- Assertions
- Using matchers
- Tagging your BDD tests
- Other
- More information
This page is 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.
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"
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)
}
}
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"))
}
}
}
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")
}
}
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
}
}
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)
}
}
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
}
}
}
}
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.)
Mark tests as pending
You can mark it and they tests as “pending”:
it("should allow removal of toppings") (pending)
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)
}
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
boomthrows an exception,interceptwill catch and return that exception. This lets you assign it to a variable likethrown. - If
boomcompletes normally, or throws an exception you weren’t expecting,interceptwill complete abruptly with aTestFailedException.
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.
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
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.
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:
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):
infomarkupalertnotebeforeandafter(I’ve had a weird problem getting these two work correctly, and need to look into it)
| this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
More information
More information on BDD testing and ScalaTest:
- A description of Behavior-Driven Development on Wikipedia
- Dan North’s original “Introducing BDD” article
- ScalaTest’s given/when/then documentation
If you wanted to see a collection of ScalaTest BDD tests all on one page, I hope this has been helpful.