Writing Assertions With Spock Framework
After we have finished this lesson, we:
- Know how we can add conditions to our feature methods.
- Can write assertions for the exceptions thrown by the system under specification.
- Know how we can use Hamcrest matchers.
This lesson assumes that:
Watch the Lesson
The text version of this lesson is given in the following:
Introduction to Assertions (aka Conditions)
One difference between Spock Framework and JUnit is that Spock framework doesn’t provide an assertion API. Instead, we can specify the expected state by using conditions. We can write conditions by following these rules:
- We have to specify the expected state in the
then
orexpect
block. - We can create conditions by using
boolean
expressions. To be more specific, we can use any expression and Groovy decides whether our expression istrue
orfalse
. - If our condition is
true
, our test will pass. - If our condition is
false
, our test will fail.
If we want to specify conditions outside of the then
and expect
blocks, we have to use the assert
keyword.
Additional Reading:
We can now create conditions with Spock Framework. Before we will put our new skills into practice, we will take a look at two rules which help us to write feature methods that are easy to read:
- We should add the most important conditions to the beginning of the
then
orexpect
block and provide a good description to these conditions. - If our conditions belong to different logical groups, we should divide our
then
orexpect
block into different sections by using theand
label. When we do this, we must provide a good description to the created sections.
Next, we will find out how we can add new conditions to the then
and expect
blocks.
Writing Assertions in the Then and Expect Blocks
As we already know, we can write assertions by adding new conditions to the then
or expect
block of our feature method. Let’s take a look at two examples that demonstrate how we can add conditions to our feature methods.
First, let’s write two conditions which verify that our feature method creates an empty list. We can do this by adding a then
block to our feature method and adding our conditions to that block.
After we have added a then
block to our feature method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class SimpleConditionSpec extends Specification { def 'Write an assertion in the then block'() { when: 'We create a new list' def list = [] then: 'Should create an empty list' list.isEmpty() list.size() == 0 } }
By the way, we don’t need both of these conditions. I used these conditions here because I wanted to demonstrate that we can use the return value of a method as a condition or specify the condition by using logical operators.
Second, let’s verify that the max()
method of the Math
class returns the greater of two numbers. We can do this by adding a new expect
block to our feature method and adding our condition to that block.
After we have added a new expect
block to our feature method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class SimpleConditionSpec extends Specification { def 'Write an assertion in the expect block'() { expect: 'Should return the bigger number' Math.max(1, 2) == 2 } }
Additional Reading:
Let’s move on and find out how we can write assertions for the exceptions thrown by the system under specification.
Writing Assertions for Exceptions
When we want to verify that the when
block of our feature method throws an exception, we have to use exception conditions. Next, we will take a look at three examples which demonstrate how we can write assertions for the exceptions thrown by the system under specification.
First, if we want to verify that the when
block of our feature method throws a specific exception, we have to use the thrown()
method and pass the type of the expected exception as a method parameter.
Let’s write a condition which verifies that the Stack
class throws the EmptyStackException
if we try to pop an empty stack. After we have written this condition, the source code of our feature method looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class ExceptionConditionSpec extends Specification { def stack = new Stack() def 'Verify that exception is thrown'() { when: 'We try to remove an element from an empty stack' stack.pop() then: 'Should throw an exception' thrown EmptyStackException } }
Second, if we want to verify that the when
block of our feature method doesn’t throw a specific exception, we have to use the notThrown()
method and pass the type of the exception as a method parameter.
Let’s write a condition which verifies that the Stack
class doesn’t throw the EmptyStackException
if we try to pop a stack that has one item. After we have written this condition, the source code of our feature method looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class ExceptionConditionSpec extends Specification { def stack = new Stack() def 'Verify that exception is not thrown'() { given: 'The stack has one item' stack.push('Hello world!') when: 'We remove an element from the stack' stack.pop() then: 'Should not throw an exception' notThrown EmptyStackException } }
Third, sometimes we want to write additional assertions for the thrown exception or we want to verify that the state of the system under specification is correct. Spock Framework supports both use cases because exception conditions can be followed by other conditions and other blocks.
Let’s ensure that our when
block throws a RuntimeException
that has the correct message. We can write the required conditions by following these steps:
- Verify that our
when
block throws aRuntimeException
and get a reference to the thrown exception object. - Ensure that the thrown exception has the correct message.
After we have written the required conditions, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(UnitTest.class) class ExceptionConditionSpec extends Specification { def 'Write assertions for the thrown exception'() { when: 'A RuntimeException is thrown with a message' throw new RuntimeException('Hello World!') then: 'Should throw RuntimeException with the correct message' def ex = thrown RuntimeException ex.message == 'Hello World!' } }
Additional Reading:
Next, we will find out how we can use Hamcrest matchers.
Using Hamcrest Matchers
Spock Framework allows us to specify conditions by using Hamcrest matchers. This is a good thing because it helps us to write conditions that are easy read, write, and maintain. When we want to write conditions by using a Hamcrest matcher, we have to use this format:
[object or value] [the used Hamcrest matcher]
Let’s verify that our feature method creates a list that has one item. We can write the required conditions by following these steps:
- Verify that the created list has one item.
- Ensure that the created list contains the correct item.
After we have written the required conditions, the source code of our feature method looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification import static org.hamcrest.Matchers.contains import static org.hamcrest.Matchers.hasSize @Category(UnitTest.class) class HamcrestConditionSpec extends Specification { def 'Write assertions with Hamcrest matchers'() { given: 'We create a new list' def list = [] when: 'A new item is added to the list' list.add('Hello World!') then: 'The list should have one item' list hasSize(1) and: 'The list should contain the correct item' list contains('Hello World!') } }
We can now add conditions to our feature methods, write assertions for the thrown exceptions, and specify conditions by using Hamcrest matchers.
Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us five things:
- We should specify our conditions by using
boolean
expressions in thethen
orexpect
blocks. - We should add the most important conditions to the beginning of the
then
orexpect
block. - If our conditions belong to different logical groups, we should divide our block into different sections by using the
and
label. - Exception conditions can be followed by other conditions and other blocks.
- If we want to use Hamcrest matchers, we have to specify our conditions by using the format:
[object or value] [the used Hamcrest matcher]
.
Previous LessonNext Lesson