It seems a reasonable thing to want to do... e.g. log something when a test fails, and not if it doesn't.
I've found this, for example, from 2013... there were no both simple and effective answers at the time. What about now?
I hoped a suitable property/method might be found in org.spockframework.runtime.SpecificationContext
... or maybe org.spockframework.runtime.model.SpecInfo
... but I can't see anything.
later
To answer the question of the sort of thing I might want to do: in fact my Specification
s "hijack" System.out
(using a PrintStream
) so I can capture output to System.out
and then analyse it. NB for the record I'm aware Spock purists may not approve of tests taking any interest in terminal output, but I'm not such a purist, specifically when talking about tests other than unit tests.
Having obtained this output in a way which means it is not output anywhere there is no reason to log it systematically and clutter up the log file ... but if a test fails I want to do so. Ditto potentially for System.err
...
First of all, logging failed tests is your test framework's (JUnit, Spock) job. So it is no big surprise that the status of the test itself is not readily available from within that test itself. Anyway, if you want something fancier in Spock, the accepted answer and also Peter's answer in the other thread are still valid. There is no simpler way of finding out whether a test has failed or not from the cleanup()
method.
Anyway, it is not as complicated as it looks because you only set it up once and then it just works. As you have not mentioned what exactly you want to log in cleanup()
, I am going to speculate a little bit. For demo purposes I am just logging the feature method name, retrieved from the specification context, and the class of the error that has occurred (so as not to print the whole fancy Spock error message twice), retrieved from a run listener registered by the global extension I am going to present here.
Global Spock extension:
The extension registers a run listener which records the error info whenever a test error occurs. At the beginning of each feature or iteration (for features with where:
blocks) the last recorded error is cleared so as not to bleed into the next feature/iteration.
package de.scrum_master.testing.extension
import org.spockframework.runtime.AbstractRunListener
import org.spockframework.runtime.extension.AbstractGlobalExtension
import org.spockframework.runtime.model.ErrorInfo
import org.spockframework.runtime.model.IterationInfo
import org.spockframework.runtime.model.SpecInfo
class TestResultExtension extends AbstractGlobalExtension {
@Override
void visitSpec(SpecInfo spec) {
spec.addListener(new ErrorListener())
}
static class ErrorListener extends AbstractRunListener {
ErrorInfo errorInfo
@Override
void beforeIteration(IterationInfo iteration) {
errorInfo = null
}
@Override
void error(ErrorInfo error) {
errorInfo = error
}
}
}
How to register the Spock extension:
You also need to add the file META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
to your test resources in order to register the extension. The file simply has this content:
de.scrum_master.testing.extension.TestResultExtension
BTW, this is not a Spock or Groovy thing but a standard Java SE feature called service providers.
Sample test using the extension:
This test is pretty stupid but shows how this works for normal methods and methods with where:
blocks both with and without @Unroll
.
package de.scrum_master.testing.extension
import spock.lang.Specification
import spock.lang.Unroll
class TestFailureReportingTest extends Specification {
def "failing normal feature"() {
expect:
0 == 1
}
def "passing normal feature"() {
expect:
0 == 0
}
def "parametrised feature"() {
expect:
a == b
where:
a << [2, 4, 6]
b << [3, 5, 6]
}
@Unroll
def "unrolled feature with #a/#b"() {
expect:
a == b
where:
a << [6, 8, 0]
b << [7, 9, 0]
}
def cleanup() {
specificationContext.currentSpec.listeners
.findAll { it instanceof TestResultExtension.ErrorListener }
.each {
def errorInfo = (it as TestResultExtension.ErrorListener).errorInfo
if (errorInfo)
println "Test failure in feature '${specificationContext.currentIteration.name}', " +
"exception class ${errorInfo.exception.class.simpleName}"
else
println "Test passed in feature '${specificationContext.currentIteration.name}'"
}
}
}
The console log (omitting the actual errors) would be:
Test failure in feature 'failing normal feature', exception class ConditionNotSatisfiedError
Test passed in feature 'passing normal feature'
Test failure in feature 'parametrised feature', exception class ConditionNotSatisfiedError
Test failure in feature 'parametrised feature', exception class ConditionNotSatisfiedError
Test passed in feature 'parametrised feature'
Test failure in feature 'unrolled feature with 6/7', exception class ConditionNotSatisfiedError
Test failure in feature 'unrolled feature with 8/9', exception class ConditionNotSatisfiedError
Test passed in feature 'unrolled feature with 0/0'
P.S.: The error info is not available yet in a cleanup:
block inside a feature method because the extension only kicks in after the whole feature/iteration including that block has finished running. So you really have to use a cleanup()
method, but you wanted that anyway and it avoids code duplication.
P.P.S.: Of course you could also just do generic logging from within the method interceptor and skip the whole cleanup()
method. But then you no longer can make your log output test-specific and it will be there for all your tests, not just the one you choose - unless of course you hard-code a package or specification name filter right into the interceptor or make sure the interceptor reads a corresponding config file when Spock starts up.