Skip to content

Commit b2433e8

Browse files
leonard84ldaley
andauthored
Add verifyEach condition (#1887)
Co-authored-by: Luke Daley <[email protected]>
1 parent 745275b commit b2433e8

20 files changed

+677
-130
lines changed

docs/include.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
:author: Peter Niederwieser, Leonard Brünings, The Spock Framework Team
22
:revnumber: X-replaced-by-gradle
33
:sourcedir: ../spock-specs/src/test/groovy/org/spockframework/docs
4+
:snapshotdir: ../spock-specs/src/test/resources/org/spockframework/docs
45
:sourcedir-spring: ../spock-spring/src/test/groovy/org/spockframework/spring/docs
56
:resourcedir-spring: ../spock-spring/src/test/resources/org/spockframework/spring/docs
67
:sourcedir-spring-boot: ../spock-spring/boot2-test/src/test/groovy/org/spockframework/boot2

docs/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ include::include.adoc[]
1717
** Requires `org.mockito:mockito-core` >= 4.11 in the test class path
1818
* Added support for mocking of static methods also for Java code with the new API `SpyStatic()` spockPull:1756[]
1919
** The <<interaction-based-testing.adoc#static-mocks,static mock methods>> will delegate the creation to the mock makers
20+
* Add <<spock_primer.adoc#verify-each,verifyEach>> method to perform assertions on each element of an `Iterable` spockPull:1887[]
2021
* Add <<extensions.adoc#parameter-injection,annotation extensions for parameters>> spockPull:1599[]
2122
* Add support for <<extensions.adoc#extension-store,keeping state in extensions>> spockPull:1692[]
2223
* Add <<extensions.adoc#spock-interceptors,feature-scoped interceptors>> spockPull:1844[]

docs/spock_primer.adoc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,89 @@ or it can be used without a target.
673673

674674
Like `with` you can also optionally define a type hint for the IDE.
675675

676+
== Using `verifyEach` to assert on each element of an `Iterable`
677+
678+
There are several ways to do assertions on `Iterable` or `Collection`.
679+
680+
=== List Equality
681+
682+
List equality is useful when the expected values are few and can be easily constructed.
683+
684+
[source,groovy,indent=0]
685+
----
686+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=simple-list-equality]
687+
----
688+
689+
will be rendered as
690+
691+
----
692+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=simple-list-equality-result]
693+
----
694+
695+
=== Set Equality
696+
697+
As with list equality, set equality is useful when the expected values are few and can be easily constructed.
698+
699+
[source,groovy,indent=0]
700+
----
701+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=simple-set-equality]
702+
----
703+
704+
will be rendered as
705+
706+
----
707+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=simple-set-equality-result]
708+
----
709+
710+
=== Every Method
711+
You can also use the standard Groovy method `every` to do assertions.
712+
The downside is, that you don't get any insight into which element failed, or why.
713+
714+
[source,groovy,indent=0]
715+
----
716+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=every-method]
717+
----
718+
719+
will be rendered as
720+
721+
----
722+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=every-method-result]
723+
----
724+
725+
[[verify-each]]
726+
=== VerifyEach Method
727+
Since 2.4, you can use the `verifyEach` method to perform assertions on every element of an iterable.
728+
In contrast to `every`, it will not stop at the first failure and instead check every element and provide a comprehensive report at the end.
729+
It won't render the full list of items, which makes it useful in cases of a large number of items.
730+
Like `with` the current item will be set as the delegate and passed as parameter to the closure.
731+
The statements in the closure will also be treated as implicit assertions.
732+
733+
[source,groovy,indent=0]
734+
----
735+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=verify-each]
736+
----
737+
738+
will be rendered as
739+
740+
----
741+
include::{snapshotdir}/primer/VerifyEachDocSpec/verifyEach_method.txt[]
742+
----
743+
744+
By default, the item will be rendered with a simple `toString()`.
745+
However, you can provide a custom "namer"-method to provide your own representation.
746+
This is useful when the item either has no useful `toString()` method or if it is too verbose.
747+
748+
[source,groovy,indent=0]
749+
----
750+
include::{sourcedir}/primer/VerifyEachDocSpec.groovy[tag=verify-each-namer]
751+
----
752+
753+
will be rendered as
754+
755+
----
756+
include::{snapshotdir}/primer/VerifyEachDocSpec/verifyEach_with_namer_method.txt[]
757+
----
758+
676759
[[specifications_as_documentation]]
677760
== Specifications as Documentation
678761

spock-core/src/main/java/org/spockframework/compiler/ConditionRewriter.java

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
/*
2-
* Copyright 2009 the original author or authors.
2+
* Copyright 2024 the original author or authors.
33
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
713
*
8-
* https://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
1514
*/
1615

1716
package org.spockframework.compiler;
1817

18+
import org.codehaus.groovy.ast.ClassNode;
19+
import org.codehaus.groovy.ast.MethodNode;
20+
import org.codehaus.groovy.ast.Parameter;
21+
import org.codehaus.groovy.ast.expr.*;
22+
import org.codehaus.groovy.ast.stmt.*;
23+
import org.codehaus.groovy.classgen.BytecodeExpression;
1924
import org.codehaus.groovy.syntax.Token;
25+
import org.codehaus.groovy.syntax.Types;
2026
import org.spockframework.compat.groovy2.GroovyCodeVisitorCompat;
2127
import org.spockframework.runtime.ValueRecorder;
22-
import org.spockframework.util.*;
28+
import org.spockframework.util.AbstractExpressionConverter;
29+
import org.spockframework.util.Assert;
30+
import org.spockframework.util.Identifiers;
31+
import org.spockframework.util.TextUtil;
2332

24-
import java.util.*;
33+
import java.util.ArrayList;
34+
import java.util.List;
2535
import java.util.regex.Pattern;
2636

27-
import org.codehaus.groovy.ast.*;
28-
import org.codehaus.groovy.ast.expr.*;
29-
import org.codehaus.groovy.ast.stmt.*;
30-
import org.codehaus.groovy.classgen.BytecodeExpression;
31-
import org.codehaus.groovy.syntax.Types;
32-
3337
import static java.util.Arrays.asList;
3438
import static java.util.Collections.singletonList;
3539
import static org.spockframework.compiler.AstUtil.createDirectMethodCall;
@@ -611,7 +615,7 @@ private Statement rewriteCondition(Expression expr, Expression message, boolean
611615
if (expr instanceof MethodCallExpression && !((MethodCallExpression) expr).isSpreadSafe()) {
612616
MethodCallExpression methodCallExpression = (MethodCallExpression)expr;
613617
String methodName = AstUtil.getMethodName(methodCallExpression);
614-
if ((Identifiers.WITH.equals(methodName) || Identifiers.VERIFY_ALL.equals(methodName))) {
618+
if ((Identifiers.CONDITION_METHODS.contains(methodName))) {
615619
return surroundSpecialTryCatch(expr);
616620
}
617621
return rewriteMethodCondition(methodCallExpression, message, explicit, optOut);

spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
/*
2-
* Copyright 2009 the original author or authors.
2+
* Copyright 2024 the original author or authors.
33
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
713
*
8-
* https://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
1514
*/
1615

1716
package org.spockframework.compiler;
1817

19-
import org.spockframework.compiler.model.*;
20-
import org.spockframework.util.*;
21-
22-
import java.util.*;
23-
2418
import org.codehaus.groovy.ast.Parameter;
2519
import org.codehaus.groovy.ast.expr.*;
26-
import org.codehaus.groovy.ast.stmt.*;
20+
import org.codehaus.groovy.ast.stmt.AssertStatement;
21+
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
22+
import org.codehaus.groovy.ast.stmt.Statement;
2723
import org.codehaus.groovy.syntax.Types;
24+
import org.spockframework.compiler.model.*;
25+
import org.spockframework.util.Identifiers;
26+
import org.spockframework.util.Nullable;
27+
28+
import java.util.List;
2829

29-
import static java.util.Arrays.asList;
3030
import static org.codehaus.groovy.ast.expr.MethodCallExpression.NO_ARGUMENTS;
3131

3232
/**
@@ -78,7 +78,7 @@ protected void doVisitExpressionStatement(ExpressionStatement stat) {
7878
InteractionRewriter rewriter = visitInteractionAwareExpressionStatement(stat);
7979

8080
if (!pastSpecialMethodCallStats.contains(stat)
81-
|| currSpecialMethodCall.isWithCall()
81+
|| currSpecialMethodCall.isConditionMethodCall()
8282
|| currSpecialMethodCall.isGroupConditionBlock()) {
8383

8484
boolean handled = handleInteraction(rewriter, stat);
@@ -192,7 +192,7 @@ private boolean handleInteraction(InteractionRewriter rewriter, ExpressionStatem
192192

193193
private boolean handleImplicitCondition(ExpressionStatement stat) {
194194
if (!(stat == currTopLevelStat && isThenOrExpectBlock()
195-
|| currSpecialMethodCall.isWithCall()
195+
|| currSpecialMethodCall.isConditionMethodCall()
196196
|| currSpecialMethodCall.isConditionBlock()
197197
|| currSpecialMethodCall.isGroupConditionBlock()
198198
|| (insideInteraction && interactionClosureDepth == 1))) {
@@ -202,18 +202,18 @@ private boolean handleImplicitCondition(ExpressionStatement stat) {
202202

203203
checkIsValidImplicitCondition(stat);
204204

205-
boolean withOrVerifyAll = Identifiers.WITH.equals(AstUtil.getMethodName(stat.getExpression()))
206-
|| Identifiers.VERIFY_ALL.equals(AstUtil.getMethodName(stat.getExpression()));
205+
String methodName = AstUtil.getMethodName(stat.getExpression());
206+
boolean isConditionMethodCall = Identifiers.CONDITION_METHODS.contains(methodName);
207207

208-
if (withOrVerifyAll) {
208+
if (isConditionMethodCall) {
209209
groupConditionFound = currSpecialMethodCall.isGroupConditionBlock();
210210
} else {
211211
conditionFound();
212212
}
213213

214-
if ((currSpecialMethodCall.isWithCall() || currSpecialMethodCall.isGroupConditionBlock())
214+
if ((currSpecialMethodCall.isConditionMethodCall() || currSpecialMethodCall.isGroupConditionBlock())
215215
&& AstUtil.isInvocationWithImplicitThis(stat.getExpression())
216-
&& !withOrVerifyAll) {
216+
&& !isConditionMethodCall) {
217217
replaceObjectExpressionWithCurrentClosure(stat);
218218
}
219219

spock-core/src/main/java/org/spockframework/compiler/ISpecialMethodCall.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
313
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
* https://www.apache.org/licenses/LICENSE-2.0
8-
* Unless required by applicable law or agreed to in writing, software
9-
* distributed under the License is distributed on an "AS IS" BASIS,
10-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11-
* See the License for the specific language governing permissions and
12-
* limitations under the License.
1314
*/
1415

1516
package org.spockframework.compiler;
@@ -34,6 +35,7 @@ public interface ISpecialMethodCall {
3435
boolean isInteractionCall();
3536

3637
boolean isWithCall();
38+
boolean isConditionMethodCall();
3739

3840
boolean isConditionBlock();
3941

@@ -49,7 +51,7 @@ public interface ISpecialMethodCall {
4951

5052
boolean isInteractionCall(MethodCallExpression expr);
5153

52-
boolean isWithCall(MethodCallExpression expr);
54+
boolean isConditionMethodCall(MethodCallExpression expr);
5355

5456
boolean isTestDouble(MethodCallExpression expr);
5557

spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
313
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
* https://www.apache.org/licenses/LICENSE-2.0
8-
* Unless required by applicable law or agreed to in writing, software
9-
* distributed under the License is distributed on an "AS IS" BASIS,
10-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11-
* See the License for the specific language governing permissions and
12-
* limitations under the License.
1314
*/
1415

1516
package org.spockframework.compiler;
1617

17-
import java.util.Collection;
18-
19-
import org.codehaus.groovy.ast.expr.*;
18+
import org.codehaus.groovy.ast.expr.ClosureExpression;
19+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
2020
import org.codehaus.groovy.ast.stmt.Statement;
2121

22+
import java.util.Collection;
23+
2224
public class NoSpecialMethodCall implements ISpecialMethodCall {
2325
public static final ISpecialMethodCall INSTANCE = new NoSpecialMethodCall();
2426

@@ -57,6 +59,11 @@ public boolean isWithCall() {
5759
return false;
5860
}
5961

62+
@Override
63+
public boolean isConditionMethodCall() {
64+
return false;
65+
}
66+
6067
@Override
6168
public boolean isConditionBlock() {
6269
return false;
@@ -93,7 +100,7 @@ public boolean isInteractionCall(MethodCallExpression expr) {
93100
}
94101

95102
@Override
96-
public boolean isWithCall(MethodCallExpression expr) {
103+
public boolean isConditionMethodCall(MethodCallExpression expr) {
97104
return false;
98105
}
99106

0 commit comments

Comments
 (0)