0

I have configured Spock Global Extension and static class ErrorListener inside it. Works fine for test errors when I want to catch feature title and errors if they happen. But how can I add some custom information to the listener?

For example I have test that calls some API. In case it fails I want to add request/response body to the listener (and report it later). Obviously I have request/response inside the feature or I can get it. How can I pass this information to the Listener and read later in the handling code?

package org.example

import groovy.json.JsonSlurper
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
import spock.lang.Specification

class OpenBrewerySpec extends Specification{

    def getBreweryTest(){
        def breweryText = new URL('https://api.openbrewerydb.org/breweries/1').text
        def breweryJson = new JsonSlurper().parseText(breweryText)
        //TODO catch breweryText for test result reporting if it is possible

        expect:
        breweryJson.country == 'United States'
    }

    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}'"
                }
    }
}

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
        }
    }
}

Create file src/test/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension and place string "org.example.TestResultExtension" there to enable extension.

yeugeniuss
  • 180
  • 1
  • 7

1 Answers1

0

I am pretty sure you found my solution here. Then you also know that it is designed to know in a cleanup() methods if the test succeeded or failed because otherwise Spock does not make the information available. I do not understand why deliberately omitted that information and posted a fragment instead of the whole method or at least mentioned where your code snippet gets executed. That is not a helpful way of asking a question. Nobody would know except for me because I am the author of this global extension.

So now after having established that you are inside a cleanup() method, I can tell you: The information does not belong into the global extension because in the cleanup() method you have access to information from the test such as fields. Why don't you design your test in such a way that whatever information cleanup() needs it stored in a field as you would normally do without using any global extensions? The latter is only meant to help you establish the error status (passed vs. failed) as such.

BTW, I even doubt if you need additional information in the cleanup() method at all because its purpose it cleaning up, not reporting or logging anything. For that Spock has a reporting system which you can also write extensions for.

Sorry for not being more specific in my answer, but your question is equally unspecific. It is an instance of the XY problem, explaining how you think you should do something instead of explaining what you want to achieve. Your sample code omits important details, e.g. the core test code as such.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks, I have updated code snippet to have all information. Right, that's a solution I have found and it works pretty well. Yes, I can store information separately in a collection and read it later in cleanup(), but it is a good idea to keep all required interception functionality in one class plus you don't need to copy and maintain additional collection in every Specification that uses this Listener. Less maintenance and overhead. Not sure if report extension can work here, as it is supposed to call TestRail API with iteration results and save information there. – yeugeniuss Jan 04 '21 at 13:42
  • Also ErrorListener is an extension of AbstractRunListener, a class with pretty comprehensive information about iteration, spec and feature run, so why not to use to handle some useful payload adhoc when you need it? I'll rename it to ResultRunLisneter to represent this later, as yes, it may be slightly confusing to use ErrorListener to handle generic feature test result. – yeugeniuss Jan 04 '21 at 13:46
  • No, you did not update your question to contain all information. The test class you added is basically the same as mine from the other answer I linked to. Neither is there any class under test you want to get information from nor is there a test doing what you explained you want to do. The actual problem description is just abstract and theoretical. Please provide a full [MCVE](https://stackoverflow.com/help/mcve), something I can compile and run without missing dependencies. A sample project on GitHub would be helpful. – kriegaex Jan 05 '21 at 02:49
  • You are not actually suggesting that the global extension should somehow contrive to access values of local variables (not even fields!) in your specification class, completely messing up any concept of encapsulation and locality, are you? In your sample code, `additionalInfo` is just a local variable. How is a global extension supposed to be able to know which local variables to report, even if it was technically possible? If you use 5 local variables in a feature method, some mocks, one with information of interest, some with information to be ignored, how would the extension know? – kriegaex Jan 05 '21 at 05:42
  • Overall yes, that what I'm trying to understand. Is it possible to pass some additional information to TestResultExtension from the Spec without creating additional static variables and supporting code in one way or another? I don't know how the extension would know what to track and what to ignore, but that' what I'm asking. – yeugeniuss Jan 06 '21 at 17:28
  • I am not sure I understand your follow-up question. Anyway, a test is not supposed to "pass" any information to an extension. The latter can tap into the test execution process in defined places and do something with information made available to it. After you edited your question all I see is that you still think the extension ought to access a local variable in a single feature method. I strongly object to this. With all due respect, it is just **plain wrong!** It is bad design, broken encapsulation. If done like this is would be unmaintainable, too. Don't do it! – kriegaex Jan 07 '21 at 05:53