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
boom
throws an exception,intercept
will catch and return that exception. This lets you assign it to a variable likethrown
. - If
boom
completes normally, or throws an exception you weren’t expecting,intercept
will 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):
info
markup
alert
note
before
andafter
(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.