At my previous workplace we worked with Groovy and Grails and I had the opportunity to use testing framework for Groovy called Spock.

Spock uses labels to divide test cases into different blocks. That way, data definition and final asserts are separated from the code under test, which is typically a function or a method call. In the article I want to show how this looks in practice and discuss adopting this style in other languages.

Let’s dive in! The basic block of code is called expect :

def "Groovy can multiple numbers"() { expect: 3*3 == 9 2*5 == 10 } 1 2 3 4 5 def "Groovy can multiple numbers" ( ) { expect : 3 * 3 == 9 2 * 5 == 10 }

The advantage of this block in Spock is that everything in that block is assumed to be an assert. Without any method invocation or a special assert keyword we get a clear section to test our assumptions.

Combining expect with where block enables us to create parametrized tests. Spock would take all our data separately and run our test multiple times:

def "Length of Spock's and his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 } 1 2 3 4 5 6 7 8 9 10 def "Length of Spock's and his friends' names" ( ) { expect : name . size ( ) == length where : name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 }

For tests that don’t need to be parametrized there is a block called setup . This block doesn’t do anything special and it just provides a visual separation for test requirements:

def "User is given id on creation"() { setup: user = createUser() expect: user.id != null } 1 2 3 4 5 6 7 def "User is given id on creation" ( ) { setup : user = createUser ( ) expect : user . id != null }

Spock also provides blocks for more BDD style test cases called begin , when and then . given is just an alias for setup , then behaves like expect and when gives us a block where we can put code under test:

def "User is given id after save"() { given: user = new User() when: user.save() then: user.id != null } 1 2 3 4 5 6 7 8 9 10 def "User is given id after save" ( ) { given : user = new User ( ) when : user . save ( ) then : user . id != null }

I really like writing test cases using these blocks because it makes the tests more readable and so I decided to write my tests in other languages in a similar style. The test frameworks that I use unfortunately don’t have anything similar available, that’s why I decided to only use simple comments for now. It helps me to visually separate what is being tested, what are the test requirements and what are the asserts.

I opted just for using given, when and then blocks for simplicity and also because without additional library support, we’d not get any other benefits anyway. Let’s compare examples in Python written with and without these code blocks:

def test_person_has_fullname(): person = Person("Jack", "Smith") assert person.fullname == "Jack Smith" def test_person_has_fullname(): # given person = Person("Jack", "Smith") # when fullname = person.fullname # then assert fullname == "Jack Smith" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def test_person_has_fullname ( ) : person = Person ( "Jack" , "Smith" ) assert person . fullname == "Jack Smith" def test_person_has_fullname ( ) : # given person = Person ( "Jack" , "Smith" ) # when fullname = person . fullname # then assert fullname == "Jack Smith"

I am sure that in this simple example many people would prefer the shorter version of the test. What we can see however is that our second version of the test reads more like a natural text because of the additional words.

More often than not, we will end up with bigger test cases that will have a couple of lines for each block:

def test_signup_email_already_used(client): email = "email@mail.com" password = "Password1" signup_user(client=client, username="username1", email=email, password=password) response = signup_user( client=client, username="username2", email=email, password=password ) assert_error(response, 422) data = json.loads(response.data) assert data["error_code"] == "INVALID_FIELD" assert "Email address is already used" in data["error_message"] def test_signup_email_already_used(client): # given email = "email@mail.com" password = "Password1" signup_user(client=client, username="username1", email=email, password=password) # when response = signup_user( client=client, username="username2", email=email, password=password ) # then assert_error(response, 422) data = json.loads(response.data) assert data["error_code"] == "INVALID_FIELD" assert "Email address is already used" in data["error_message"] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def test_signup_email_already_used ( client ) : email = "email@mail.com" password = "Password1" signup_user ( client = client , username = "username1" , email = email , password = password ) response = signup_user ( client = client , username = "username2" , email = email , password = password ) assert_error ( response , 422 ) data = json . loads ( response . data ) assert data [ "error_code" ] == "INVALID_FIELD" assert "Email address is already used" in data [ "error_message" ] def test_signup_email_already_used ( client ) : # given email = "email@mail.com" password = "Password1" signup_user ( client = client , username = "username1" , email = email , password = password ) # when response = signup_user ( client = client , username = "username2" , email = email , password = password ) # then assert_error ( response , 422 ) data = json . loads ( response . data ) assert data [ "error_code" ] == "INVALID_FIELD" assert "Email address is already used" in data [ "error_message" ]

In this example we can see that the difference is noticable.

Even if this technique is just visual and doesn’t necessary carry any meaning in terms of code, I enjoy it and I find myself using it these days.

I looked whether there is a Spock-like testing framework or library available in the world of Python or JavaScript and the only one seems to be nimoy for Python. I haven’t personally tried it since I am using Pytest, but it seems that I am not the only one who enjoyed working with Spock.

So what do you think? Do you have any favourite DSL or a way to write tests? Let me know in the comments.

Also, if you are into testing and discussing best practices, maybe you will enjoy my other article Putting Test Case Specification directly in code.

Happy testing!