The >>>
operator allows a sequence of values to be returned:
myMock.someCall() >>> ['first value', 'second value', 'third value', 'etc']
This returns each string in turn. Behaviour (such as throwing exceptions) in closures cannot be used by this operator.
The >>
operator allows value or behaviour (closures) to be returned
myMock.someCall() >> 'first value' >> { generatorMethod() } >> 'third value' >> { throw new EmptyStackException() }
Each operator returns a single value or closure, but they can be chained (and intermixed with the >>>
operator)
There are a number of different approaches that can be used. Firstly a reminder of the basic assertions that can be applied to parameters:
Note that these are predicates, not assertions (i.e. they fail by not matching rather than asserting the argument value).
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)
Combining multiple predicate conditions:
1 * worker.doSomething({it.content == 'foo' && it.id > 10})
Or a version with a named parameter for multiple predicates:
1 * eventRecorder.recordEvent({
event ->
event.getTimestamp() != null &&
event.getType() == Event.Type.REMINDER_SENT &&
event.getCustomerName() == "Susan Ivanova"
})
Note the &&
combining the predicate conditions and the lack of an assert
as these are predicates, not assertions!
Create explicit assertions on the right hand side - note that using this format allows different assertions to be applied on each method invocation. Remember that if the method needs a return value it needs to be provided at the end of the closure.
Where there is a single argument:
1 * collaborator.someMethod(_) >> { int aParam ->
assert aParam == 10
// any other assertions...
}
or the single argument from of the array capture (below)
1 * basketsRepository.save(_) >> { arguments ->
final Apple apple = arguments.get(0)
assert apple == new Apple(weight: 0.123)
}
For several arguments capture all the arguments as an array:
1 * greeting.sayHello(*_) >> { arguments ->
final List<Person> argumentPeople = arguments[0]
assert argumentPeople == [new Person(name: 'george'), new Person(name: 'ringo')]
}
Arguments can be captured for later use in assertions (careful: this may not lead to readable code)
given:
Apple apple
1 * basketsRepository.save(_) >> { arguments ->
apple = arguments[0]
}
when:
// ...
then:
assert apple == new Apple(weight: 0.123)
Specify the order in which interactions must take place by adding then
sections:
then:
1 * service.save(_ as User)
then:
1 * transaction.commit()
Allows a list of objects to easily be tested for a property of each object:
then:
myBooks*.name == ['Guns, Germs & Steel','Jitterbug Perfume']
myBooks*.publisher*.name == ['Penguin','Walker']
###Using Hamcrest
Compare Lists Ignoring Order (Hamcrest Integration)
build.gradle:
dependencies {
testCompile 'org.hamcrest:hamcrest-library:1.3'
}
test.spock:
import org.hamcrest.Matchers.*
import static org.hamcrest.Matchers.containsInAnyOrder
import static spock.util.matcher.HamcrestSupport.that
def "Some test that should check results in any order"() {
// ...
then:
that( listThatNeedsToBeTested, containsInAnyOrder( firstControl, secondControl, thirdControl, etc))
A statically typed variable whose get() method will block until some other thread has set a value with the set() method, or a timeout expires.
Optional constructor parameters:
- timeout: Failure timeout. Default 1 second
def machine = new Machine()
def result = new BlockingVariable<WorkResult>
// register async callback
machine.workDone << { r ->
result.set(r)
}
when:
machine.start()
then:
// blocks until workDone callback has set result, or a timeout expires
result.get() == WorkResult.OK
Alternative to BloackingVariable. Rather than transferring state to an expect- or then-block, it is verified right where it is captured. On the upside, this can result in a more informative stack trace if the evaluation of a condition fails. On the downside, the coordination between threads is more explicit.
Optional constructor parameters:
- numEvalBlocks: number of evaluate blocks (corresponds to the number of
evaluate { }
calls that the test contains)
def machine = new Machine()
def conds = new AsyncConditions()
// register async callback
machine.workDone << { result ->
conds.evaluate {
assert result == WorkResult.OK
// could add more explicit conditions here
}
}
when:
machine.start()
then:
// opional number of seconds. Default 1 second
conds.await( 5.0)
Repeatedly evaluates one or more conditions until they are satisfied or a timeout has elapsed.
Optional constructor parameters:
- initialDelay: Initial delay before first poll. Default 0 seconds
- timeout: Failure timeout. Default 1 second
- delay: Delay between evaluation polls. Default 0.1 seconds
- factor: Backoff factor for delays. Default 1
def conditions = new PollingConditions(timeout: 10, initialDelay: 1.5, factor: 1.25)
def machine = new Machine()
when:
machine.start()
then:
// assert additional conditions within a time (secs)
conditions.within( 2.0) {
assert machine.temperature >= 100
}
// await the final set of conditions
conditions.eventually {
assert machine.efficiency >= 0.9
}