Skip to content

Commit 3ee054e

Browse files
committed
Support streams in verifyEach
Add verifyEach overloads accepting java.util.stream.Stream (with and without a namer), adapting the single-use stream to an Iterable that is iterated exactly once.
1 parent 514f39d commit 3ee054e

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

docs/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ include::include.adoc[]
99
=== Enhancements
1010

1111
* Improve `verifyEach` to also accept object arrays, not only `Iterable`
12+
* Improve `verifyEach` to also accept a `java.util.stream.Stream`
1213
* Improve `verifyEach` to accept a `Map`, iterating its entries with Groovy's key/value destructuring
1314
* Add support for `final` local variables in `where:` blocks, declared at their beginning and evaluated once per feature, scoped to the where-block spockIssue:138[]
1415
* Improve `TooManyInvocationsError` now reports unsatisfied interactions with argument mismatch details, making it easier to diagnose why invocations didn't match expected interactions spockPull:2315[]

spock-core/src/main/java/spock/lang/Specification.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Map;
2929
import java.util.Objects;
3030
import java.util.function.Function;
31+
import java.util.stream.Stream;
3132

3233
import org.spockframework.lang.ISpecificationContext;
3334
import org.spockframework.lang.Wildcard;
@@ -424,6 +425,60 @@ public <U> void verifyEach(
424425
verifyEach(Arrays.asList(things), namer, closure);
425426
}
426427

428+
/**
429+
* Performs assertions on each element of a stream, collecting up failures instead of stopping at first.
430+
* <p>
431+
* Exception messages will contain a toString() of the element to identify it.
432+
* <p>
433+
* The closure can either use one or two parameters.
434+
* The first parameter will always be the element.
435+
* The second optional parameter will be the iteration index of the element.
436+
*
437+
* @param things the stream to inspect
438+
* @param closure a code block containing top-level conditions
439+
* @param <U> type of elements in things
440+
* @since 2.5
441+
*/
442+
@Beta
443+
public <U> void verifyEach(
444+
Stream<U> things,
445+
@ClosureParams(value = FromString.class, options = {"U", "U, int"})
446+
@DelegatesTo(type = "U", strategy = Closure.DELEGATE_FIRST)
447+
Closure<?> closure
448+
) {
449+
verifyEach(things, Objects::toString, closure);
450+
}
451+
452+
/**
453+
* Performs assertions on each element of a stream, collecting up failures instead of stopping at first.
454+
* <p>
455+
* Exception messages will contain the result of calling the namer for an element to identify it.
456+
* <p>
457+
* The closure can either use one or two parameters.
458+
* The first parameter will always be the element.
459+
* The second optional parameter will be the iteration index of the element.
460+
*
461+
* @param things the stream to inspect
462+
* @param namer the namer function to use when rendering the exception
463+
* @param closure a code block containing top-level conditions
464+
* @param <U> type of elements in things
465+
* @since 2.5
466+
*/
467+
@Beta
468+
public <U> void verifyEach(
469+
Stream<U> things,
470+
Function<? super U, ?> namer,
471+
@ClosureParams(value = FromString.class, options = {"U", "U, int"})
472+
@DelegatesTo(type = "U", strategy = Closure.DELEGATE_FIRST)
473+
Closure<?> closure
474+
) {
475+
if (things == null) {
476+
throw new SpockAssertionError("Target of 'verifyEach' block must not be null");
477+
}
478+
// a Stream is single-use, so adapt it to an Iterable that iterates it exactly once
479+
verifyEach((Iterable<U>) things::iterator, namer, closure);
480+
}
481+
427482
/**
428483
* Performs assertions on each entry of a map, collecting up failures instead of stopping at first.
429484
* <p>

spock-specs/src/test/groovy/org/spockframework/smoke/VerifyEachBlocks.groovy

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,65 @@ class VerifyEachBlocks extends EmbeddedSpecification {
210210
e.message == "Target of 'verifyEach' block must not be null"
211211
}
212212

213+
def "verifyEach supports streams"() {
214+
given:
215+
def stream = [1, 2, 3].stream()
216+
217+
expect:
218+
verifyEach(stream) {
219+
it > 0
220+
}
221+
}
222+
223+
def "verifyEach on a stream handles a failed element verification"() {
224+
given:
225+
def stream = [1, 2, 3].stream()
226+
227+
when:
228+
verifyEach(stream) {
229+
it < 3
230+
}
231+
232+
then:
233+
SpockAssertionError e = thrown()
234+
e.message.contains('item[2] 3')
235+
}
236+
237+
def "verifyEach on a stream supports the namer"() {
238+
given:
239+
def stream = [1, 2, 3].stream()
240+
241+
when:
242+
verifyEach(stream, { "int($it)" }) {
243+
it < 3
244+
}
245+
246+
then:
247+
SpockAssertionError e = thrown()
248+
e.message.contains('int(3)')
249+
}
250+
251+
def "verifyEach on a stream can have an optional index parameter"() {
252+
given:
253+
def stream = [1, 2, 3].stream()
254+
255+
expect:
256+
verifyEach(stream) { it, index ->
257+
it == index + 1
258+
}
259+
}
260+
261+
def "verifyEach on a null stream fails with a clear message"() {
262+
when:
263+
verifyEach((java.util.stream.Stream) null) {
264+
it > 0
265+
}
266+
267+
then:
268+
SpockAssertionError e = thrown()
269+
e.message == "Target of 'verifyEach' block must not be null"
270+
}
271+
213272
def "verifyEach supports maps with a key and value parameter"() {
214273
given:
215274
def map = [a: 1, b: 2, c: 3]

0 commit comments

Comments
 (0)