Skip to content

Commit

Permalink
Clarified documentation about Iterable data providers and size() calls (
Browse files Browse the repository at this point in the history
#2027)

Added note to the documentation to clarify the usage of Iterables
as data pipes and the used size() method.


Fixes #2022
  • Loading branch information
AndreasTu authored Nov 5, 2024
1 parent 766f8dc commit bf771f1
Show file tree
Hide file tree
Showing 3 changed files with 180 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 @@ -25,6 +25,7 @@ include::include.adoc[]
* Fix mocking of final classes via `@SpringBean` and `@SpringSpy` spockIssue:1960[]
* Size of data providers is no longer calculated multiple times but only once
* Fix exception when using `@RepeatUntilFailure` with a data provider with unknown iteration amount. spockPull:2031[]
* 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,174 @@
/*
* 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 == 1
}

@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 == 2
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 == 1
}

@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.runFeatureBody """
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 bf771f1

Please sign in to comment.