If you are not familiar with spock it is testing framework for Groovy and Java. It’s been stable for quite some time and I highly recommend you to check it out if you are annoyed by Junit and Java’s style of writing tests.
What’s wrong with JUnit + <some mocking framework> ?
The standard way of testing Java application is to use Junit and some mocking framework (Mockito,EasyMock, PowerMock etc.).
Java combined with those frameworks makes it rather hard to write and read tests in medium and large sizes projects:
- You cannot set title for a test (Junit5 introduces this feature but it is still in alpha). Instead you have to name your method in a ridiculous way like ‘shouldAddToCartIfItemIsAvailaibleAndTheLimitIsNotExceededAnd…..’.
- The intent of the test is blurred by all those Java and mocking framework verbosity like Collections.singletonList()’s,replay’s,verify’s or any(MyAwesomeAbstractFactoryBaseClass.class)’s and many more.
- There is no good way to separate sections responsible for different phases (given,when,then). Some people use comments to mark those sections but I think it’s even worse than not having them at all.
- Java is certainly not easy language for building “expected” objects - everything is so verbose. Once again - it hides the intent of a test.
- Parametrizied tests are kinda weird too. They must be stored in fields, and you can only have one set of them per test class.
- Since parametrized test are not simple you usually
write gazillion of separate methods - each covering different case, or even worse skip some cases hoping noone will notice :).
If tests are hard to write we usually think of them as something painful and start to neglect them. Avoiding or delaying writing tests leads to the situation where application cannot be trusted anymore. We then become afraid of making any changes because other part of the app might break in some bizarre way.
It shouldn’t be this way. Test should be easy and fun to write. After all they are like a cherry on top, proving that the features are implemented correctly.
In my opinion the most important responsibility of the test is to be as most readable as possible. Business changes to the project are introduced all the time. If we change something in the application we have to change test too (unless you’re applying open-closed principle, which I’ve never heard of anyone successfully adapting :D). If tests are hard to read there is a big problem.
On the other hand - these are just my opinion, who am I to judge? Do you feel similar about this topic or is it just me? If you disagree, or have some objections leave a comment!
Spock
Spock is both testing and mocking framework. What’s more it extends Junit runner so it can be runned by the tools you used for your tests before.
The best thing about Spock is that it’s basically a DSL (domain specifing language) for writing tests. It’s based on Groovy and is designed particularly testing. It introduces some syntax features just for that purpose. You may therefore expect some neat stuff in it (which is indeed correct).
Groovy is kinda like a scripting version of Java - simple, less verbose but retains all the power of JVM.
Benefits from using spock over Junit + mocking framework:
- Groovy - less verbose than Java
- Additional syntax features designed for testing
- Integrated stubbing and mocking
- Extends Junit runner
- Easy parametrized testing
- Labels for all phases of a test (given,when,then…)
- Ability to document methods easily (unlike weird Junit method’s name pattern)
- Many more specified below
Cheatsheet
This cheatsheet contains the most useful spock features regarding testing Java applications. Most of this is copy-paste from official spock documentation. I compiled it while I was learning the framework to have all information in one place. I figure out since it’s already compiled why not share it on a blog too.
Basics
Specification template
class MyFirstSpecification extends Specification {
// fields
// fixture methods
// feature methods
// helper methods
}
Fixture Methods
def setup() {} // run before every feature method
def cleanup() {} // run after every feature method
def setupSpec() {} // run before the first feature method
def cleanupSpec() {} // run after the last feature method
Blocks order
given: //data initialization goes here (includes creating mocks)
when: //invoke your test subject here and assign it to a variable
then: //assert data here
cleanup: //optional
where: //optional:provide parametrized data (tables or pipes)
or
given:
expect: //combines when with then
cleanup:
where:
Junit comparison
Spock | JUnit |
---|---|
Specification | Test class |
setup() | @Before |
cleanup() | @After |
setupSpec() | @BeforeClass |
cleanupSpec() | @AfterClass |
Feature | Test |
Feature method | Test method |
Data-driven feature | Theory |
Condition | Assertion |
Exception condition | @Test(expected=…) |
Interaction | Mock expectation (e.g. in Mockito) |
Data Driven Testing
Data Tables
class Math 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 //passes
7 | 4 | 4 //fails
0 | 0 | 0 //passes
}
}
Input data can also be seperated with expected parameters using ||
:
where:
a | b || c
3 | 5 || 5
7 | 0 || 7
0 | 0 || 0
Unrolling
A method annotated with @Unroll will have its rows from data table reported independently:
@Unroll
def "maximum of two numbers"() { ... }
With unroll
maximum of two numbers[0] PASSED
maximum of two numbers[1] FAILED
Math.max(a, b) == c
| | | | |
| 7 0 | 7
42 false
Without unroll
We have to figure out which row failed manually
maximum of two numbers FAILED
Condition not satisfied:
Math.max(a, b) == c
| | | | |
| 7 0 | 7
42 false
Data Pipes
Right side must be Collection, String or Iterable.
where:
a << [3, 7, 0]
b << [5, 0, 0]
c << [5, 7, 0]
Multi-Variable Data Pipes
where:
[a, b, c] << sql.rows("select a, b, c from maxdata")
where:
row << sql.rows("select * from maxdata")
// pick apart columns
a = row.a
b = row.b
c = row.c
Ignore some variable
where:
[a,b] << [[1,2,3],[1,2,3],[4,5,6]]
[a, b, _, c] << sql.rows("select * from maxdata")
Combine data tables,pipes and assignments
where:
a | _
3 | _
7 | _
0 | _
b << [5, 0, 0]
c = a > b ? a : b
Unrolled method names parameters
def "#person is #person.age years old"() { ... } // property access
def "#person.name.toUpperCase()"() { ... } // zero-arg method call
Interaction Based Testing
Mocking
Create mock
Mocks are Lenient (return default value for undefined mock calls)
Subscriber subscriber = Mock()
def subscriber2 = Mock(Subscriber)
Using mock
def "should send messages to all subscribers"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello") //subsriber should call receive with "hello" once.
1 * subscriber2.receive("hello")
}
Cardinality
1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive)
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls, including zero
// (rarely needed; see 'Strict Mocking')
Constraints
Target
1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello") // a call to any mock object
Method
1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello") // a method whose name matches the given regular expression
// (here: method name starts with 'r' and ends in 'e')
Argument
1 * subscriber.receive("hello") // an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument that is unequal to the String "hello"
1 * subscriber.receive() // the empty argument list (would never match in our example)
1 * subscriber.receive(_) // any single argument (including null)
1 * subscriber.receive(*_) // any argument list (including the empty argument list)
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 the given predicate
// (here: message length is greater than 3)
Specify mock calls at creation
class MySpec extends Specification {
Subscriber subscriber = Mock {
1 * receive("hello")
1 * receive("goodbye")
}
}
Group interactions
with(mock) {
1 * receive("hello")
1 * receive("goodbye")
}
Stubbing
Stubs do not have cardinality (matches invokation anyTimes)
def subsriber = Stub(Subscriber)
...
subscriber.receive(_) >> "ok"
Whenever the subscriber receives a message, make it respond with ‘ok’
Returning different values on sucessive calls
subscriber.receive(_) >>> ["ok", "error", "error", "ok"]
subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"
Extensions
@Ignore(reason = "TODO")
@IgnoreRest
@IgnoreIf({ spock.util.environment.Jvm.isJava5()) })
@Requires({ os.windows })
@Timeout(5)
@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
@Title("This tests if..."
@Narrative("some detailed explanation")
@Issue("http://redmine/23432")
@Subject