Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language. Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers. Spock is inspired from JUnit, jMock, RSpec, Groovy, Scala, Vulcans, and other fascinating life forms.
This presentation is an overview of the Spock 1.1 documentation available at http://docs.spockframework.org/
21. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(1, 3) == 3
Math.max(7, 4) == 7
Math.max(0, 0) == 0
}
}
The naive way
Data Driven Testing
• Code and data are mixed and cannot
easily be changed independently
• Data cannot easily be auto-generated or
fetched from external sources
• In order to exercise the same code
multiple times, it either has to be
duplicated or extracted into a separate
method
• In case of a failure, it may not be
immediately clear which inputs caused
the failure
• Exercising the same code multiple times
does not benefit from the same isolation
as executing separate methods does
…has potential drawbacks:
22. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(1, 3) == 3
Math.max(7, 4) == 7
Math.max(0, 0) == 0
}
}
Refactored
Data Driven Testing
23. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
}
}
Refactored
Data Driven Testing
24. class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
}
}
Refactored
Data Driven Testing
25. class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
...
}
}
Refactored
Data Driven Testing
26. class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
}
}
Data Tables
Data Driven Testing
27. class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
Data Tables
Data Driven Testing
28. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
Data Tables
Data Driven Testing
method parameters omitted
29. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
}
Data Tables
Data Driven Testing
visually seperate inputs and outputs
30. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
}
Data Tables with visually seperated inputs and outputs
Data Driven Testing
31. class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
}
Reporting of failures
Data Driven Testing
maximum of two numbers FAILED
Condition not satisfied:
Math.max(a, b) == c
| | | | |
| 7 4 | 7
42 false
32. @Unroll
def "maximum of two numbers"()
Reporting of failures
Data Driven Testing
maximum of two numbers[0] PASSED
maximum of two numbers[1] FAILED
Math.max(a, b) == c
| | | | |
| 7 4 | 7
42 false
maximum of two numbers[2] PASSED
33. @Unroll
def "maximum of #a and #b is #c"()
Reporting of failures
Data Driven Testing
maximum of 3 and 5 is 5 PASSED
maximum of 7 and 4 is 7 FAILED
Math.max(a, b) == c
| | | | |
| 7 4 | 7
42 false
maximum of 0 and 0 is 0 PASSED
34. ...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]
Data pipes
Data Driven Testing
...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b
35. @Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
[a, b, c] << sql.rows("select a, b, c from maxdata")
}
Data pipes
Data Driven Testing
48. 1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls
Cardinality
Interaction Based Testing
49. 1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls
Cardinality
Interaction Based Testing
50. 1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello") // a call to any mock object
Target Constraint
Interaction Based Testing
51. 1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello") // matches regular expression
Method Constraint
Interaction Based Testing
52. 1 * subscriber.receive("hello") // an argument equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument unequal to the String "hello"
1 * subscriber.receive() // the empty argument list
1 * subscriber.receive(_) // any single argument (including null)
1 * subscriber.receive(*_) // any argument list (including empty)
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive({ it.size() > 3 }) // an argument that satisfies predicate
Argument Constraints
Interaction Based Testing
53. 1 * subscriber.receive("hello") // an argument equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument unequal to the String "hello"
1 * subscriber.receive() // the empty argument list
1 * subscriber.receive(_) // any single argument (including null)
1 * subscriber.receive(*_) // any argument list (including empty)
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive({ it.size() > 3 }) // an argument that satisfies predicate
Argument Constraints
Interaction Based Testing
1 * process.invoke("ls", "-a", _, !null,
{ ["abcdefghiklmnopqrstuwx1"].contains(it) })
57. Verification of Interactions
Interaction Based Testing
Too many invocations for:
2 * subscriber.receive(_) (3 invocations)
Matching invocations (ordered by last occurrence):
2 * subscriber.receive("hello") <-- this triggered the error
1 * subscriber.receive("goodbye")
58. Verification of Interactions
Interaction Based Testing
Too few invocations for:
1 * subscriber.receive("hello") (0 invocations)
Unmatched invocations (ordered by similarity):
1 * subscriber.receive("goodbye")
1 * subscriber2.receive("hello")
66. @Timeout(5)
def "I fail if I run for more than five seconds"() { ... }
@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
def "I better be quick" { ... }
Timeouts
Extensions
67. @Narrative("""
As a user
I want foo
So that bar
""")
@See("http://spockframework.org/spec")
class MySpec {
@Issue("http://my.issues.org/FOO-2")
def "Foo should do bar"() { ... }
}
Documentation
Extensions
68. def "should send emails to matching users"() {
given:
def user1 = new User(id: 1L, userName: 'user one', email: 'test@user.one')
def user2 = new User(id: 2L, userName: 'user two', email: ‘test@user.two')
def user3 = new User(id: 3L, userName: 'THIRD test user', email: 'test@user.three')
def user4 = new User(id: 4L, userName: 'testing user four', email: 'test@user.four')
def subject = 'some test email subject'
def content = 'some content of the email'
when:
def result = underTest.sendEmailsToUsers(subject, content, 'test')
then: "get users from repository and send email to each matching user"
1 * userRepository.findAll() >> [user1, user2, user3, user4]
1 * emailSender.sendEmail(subject, content, 'test@user.one')
1 * emailSender.sendEmail(subject, content, 'test@user.four')
result == ['test@user.one', 'test@user.four']
}
@Test
public void testSendingEmailsToMatchingUsers() {
//given:
User user1 = new User();
user1.setId(1L);
user1.setUserName("test user one");
user1.setEmail("test@user.one");
User user2 = new User();
user2.setId(2L);
user2.setUserName("test user two");
user2.setEmail("test@user.two");
User user3 = new User();
user3.setId(3L);
user3.setUserName("THIRD test user");
user3.setEmail("test@user.three");
User user4 = new User();
user4.setId(4L);
user4.setUserName("testing user four");
user4.setEmail("test@user.four");
String subject = "some test email subject";
String content = "some content of the email";
when(userRepository.findAll()).thenReturn(Arrays.asList(user1, user2, user3, user4));
//when:
List<String> result = underTest.sendEmailsToUsers(subject, content, "test");
//then:
Assert.assertEquals(Arrays.asList("test@user.one", "test@user.four"), result);
verify(userRepository).findAll();
}
Mockito vs Spock