Skip to content

Commit

Permalink
Clarified documentation about Iterable data providers and size call
Browse files Browse the repository at this point in the history
Added note to the documentation to clarify the usage of Iterables
as data pipes and the used size() method.

Fixes spockframework#2022
  • Loading branch information
AndreasTu committed Nov 3, 2024
1 parent 12a9450 commit 76d53f9
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/data_driven_testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ used as a data provider. This includes objects of type `Collection`, `String`, `
they can fetch data from external sources like text files, databases and spreadsheets, or generate data randomly.
Data providers are queried for their next value only when needed (before the next iteration).

NOTE: Spock uses the `size()` method to calculate the amount of iterations,
except for data providers that implement `Iterator`,
so make sure `size()` is working efficient, or supply an `Iterator` if that is not possible.


== Multi-Variable Data Pipes

If a data provider returns multiple values per iteration (as an object that Groovy knows how to iterate over),
Expand Down
1 change: 1 addition & 0 deletions docs/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ include::include.adoc[]
** This allows the use of these classes in an OSGi environment, where the class imports in the embedded spec are not visible to the Spock OSGi bundle ClassLoader
* Fix mocking issue with the ByteBuddy MockMaker when using multiple classloaders in Java 21 spockIssue:2017[]
* Fix mocking of final classes via `@SpringBean` and `@SpringSpy` spockIssue:1960[]
* Clarified documentation about data providers and `size()` calls spockIssue:2022[]

== 2.4-M4 (2024-03-21)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.spockframework.datapipes

import org.spockframework.EmbeddedSpecification
import spock.lang.Issue
import spock.util.EmbeddedSpecRunner

import static java.util.Objects.requireNonNull

class DataPipesIteratorSpec extends EmbeddedSpecification {

private static final ThreadLocal<Object> currentDataProvider = new ThreadLocal<>();

def cleanup() {
currentDataProvider.remove()
}

def "Collection data provider will use size method to estimate number of iterations"() {
given:
def dataCollection = dataProvider(new TestDataCollection())

when:
def res = runIterations()

then:
res.testsSucceededCount == 2
dataCollection.iteratorCalls == 1
dataCollection.sizeCalls == 2
}

@Issue("https://github.com/spockframework/spock/issues/2022")
def "Iterable uses the Groovy default size method to estimate number of iterations"() {
given:
def dataIterable = dataProvider(new TestDataIterable())

when:
def res = runIterations()

then:
dataIterable.iteratorCalls == 3
res.testsSucceededCount == 2
}

@Issue("https://github.com/spockframework/spock/issues/2022")
def "Iterable with size method shall use the size method to estimate number of iterations"() {
given:
def dataIterable = dataProvider(new TestDataIterableWithSize())

when:
def res = runIterations()

then:
res.testsSucceededCount == 2
dataIterable.iteratorCalls == 1
dataIterable.sizeCalls == 2
}

@Issue("https://github.com/spockframework/spock/issues/2022")
def "Iterator shall be only called once"() {
given:
def dataIterator = dataProvider(new TestDataIterator())

when:
def res = runIterations()

then:
res.testsSucceededCount == 2
dataIterator.hasNextCalls == 3
dataIterator.nextCalls == 1
}

private EmbeddedSpecRunner.SummarizedEngineExecutionResults runIterations() {
runner.runSpecBody """
def "run"(Object input){
expect:
input != null
where:
//noinspection UnnecessaryQualifiedReference
input << org.spockframework.datapipes.DataPipesIteratorSpec.getDataProvider()
}
"""
}

private static <T> T dataProvider(T dataProvider) {
currentDataProvider.set(requireNonNull(dataProvider))
return dataProvider
}

static Object getDataProvider() {
def data = currentDataProvider.get()
assert data != null
return data
}

private static class TestDataCollection extends AbstractCollection {
int iteratorCalls = 0
int sizeCalls = 0

@Override
Iterator iterator() {
iteratorCalls++
return ["Value"].iterator()
}

@Override
int size() {
sizeCalls++
return 1
}
}

private static class TestDataIterableWithSize implements Iterable {
int iteratorCalls = 0
int sizeCalls = 0

int size() {
sizeCalls++
return 1
}

@Override
Iterator iterator() {
iteratorCalls++
return ["Value"].iterator()
}
}

private static class TestDataIterable implements Iterable {
int iteratorCalls = 0

@Override
Iterator iterator() {
iteratorCalls++
return ["Value"].iterator()
}
}

private static class TestDataIterator implements Iterator {
int hasNextCalls = 0
int nextCalls = 0

@Override
boolean hasNext() {
hasNextCalls++
if (nextCalls == 0) {
return true
}
return false
}

@Override
Object next() {
nextCalls++
if (nextCalls == 1) {
return "Value"
}
throw new NoSuchElementException()
}
}
}

0 comments on commit 76d53f9

Please sign in to comment.