Re: [geb-user] Ability to skip setup() for certain tests

66 views
Skip to first unread message
Message has been deleted

Marcin Erdmann

unread,
May 3, 2020, 8:22:15 AM5/3/20
to geb-...@googlegroups.com
Jeremy,

I thought that you asked this question once before and after searching through the past messages to the list from you it turned out that I was right. I will just link to my reply to your earlier email on the topic: https://groups.google.com/d/msg/geb-user/7vIvuB64CFw/CvcUVnxmFQAJ. Personally, I'd consider the need to skip the setup method on one or two tests a smell as well as an indicator that these tests do not belong in the spec in question and should probably be moved out to a separate spec.

Marcin

On Fri, May 1, 2020 at 6:50 PM jc <jeremy...@gmail.com> wrote:
I think a good enhancement would be the ability to skip the setup() for particular tests.  For example I have 20 tests but 1 or 2 of them are slightly different and don't need the setup().  Perhaps a flag of 
setup: false
 
on the method or something like that.  Just throwing the idea out there as I have had several time this has come up for me.  We can easily get around it by just creating a method of what we want and putting it in all the tests except the ones that don't need it but I thought maybe others have come across this.  Just want to throw an enhancement idea out there.

--
You received this message because you are subscribed to the Google Groups "Geb User Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to geb-user+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/geb-user/306afc9f-0883-4a40-b16c-e786dbfde9a4%40googlegroups.com.

jc

unread,
May 3, 2020, 8:28:18 AM5/3/20
to Geb User Mailing List
Wow, wonder why i don't remember that. My mistake!

Alexander Kriegisch

unread,
May 3, 2020, 12:22:57 PM5/3/20
to geb-...@googlegroups.com

Hi Jeremy.

I agree with Marcin in everything he says. You really should refactor the way he suggested. And as he also said the first time he answered your question, this is a Spock rather than a Geb question.

But for what it is worth, I was a little bit bored just now and wanted to find out if an annotation-driven extension could solve the problem. Actually to quickly hack

  • an annotation @SkipSetup which you can add to any feature method and
  • a SkipSetupExtension extends AbstractAnnotationDrivenExtension<SkipSetup> with a visitFeatureAnnotation() method which gets triggered each time an annotated feature method is intercepted

is a matter of two minutes.

The complication here is the fact that actually nothing specific should happen in the annotated method itself but in the feature's setup() method which gets executed at another time (later, more precisely). So we have to find a way to communicate to the setup() method that we want to skip it. The way I did it is a bit contrived, but easy enough to implement:

  • The first time our annotation-driven extension gets triggered (i.e. the first time Spock finds a feature method annotated by @SkipSetup), we create a special method interceptor which only intercepts setup() methods.
  • We add that interceptor instance to the SpecInfo and also save a reference to it in the SkipSetupExtension in order to avoid creating a new interceptor each time we meet a @SkipSetup annotation. We only want to do that once.
  • The interceptor itself gets a List<String> skippedFeatures property which we update with each newly found feature method name carrying the marker annotation.
  • During runtime the interceptor's interceptSetupMethod() method checks if it finds the current feature method name in its skippedFeatures list. Only if it does not, it proceeds to the setup method, otherwise it skips by just doing nothing.

Here is the source code for

  • the annotation,
  • the extension,
  • the interceptor and
  • two almost identical test classes (because we want to check if really one extension instance is created per specification so as not to get into trouble with our internal references) in order to check if it works as expected:

package de.scrum_master.testing

import org.spockframework.runtime.extension.ExtensionAnnotation

import java.lang.annotation.Retention
import java.lang.annotation.Target

import static java.lang.annotation.ElementType.METHOD
import static java.lang.annotation.RetentionPolicy.RUNTIME

@Retention(RUNTIME)
@Target(METHOD)
@ExtensionAnnotation(SkipSetupExtension)
@interface SkipSetup {}

package de.scrum_master.testing

import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.FeatureInfo

class SkipSetupExtension extends AbstractAnnotationDrivenExtension<SkipSetup> {
SkipSetupMethodInterceptor interceptor

@Override
void visitFeatureAnnotation(SkipSetup annotation, FeatureInfo feature) {
if (!interceptor) {
interceptor = new SkipSetupMethodInterceptor()
feature.spec.addSetupInterceptor interceptor
}
interceptor.skippedFeatures << feature.name
}
}

package de.scrum_master.testing
import org.spockframework.runtime.extension.AbstractMethodInterceptor
import org.spockframework.runtime.extension.IMethodInvocation

class SkipSetupMethodInterceptor extends AbstractMethodInterceptor {
List<String> skippedFeatures = new LinkedList<>()

@Override
void interceptSetupMethod(IMethodInvocation invocation) throws Throwable {
if (!skippedFeatures.contains(invocation.feature.name))
invocation.proceed()
}
}

package de.scrum_master.testing

import spock.lang.Specification

class SkipSetupTestA extends Specification {
def setup() {
println "SkipSetupTestA -> setup"
}

def feature1() {
println "SkipSetupTestA -> feature1"
expect: true
}

@SkipSetup
def feature2() {
println "SkipSetupTestA -> feature2"
expect: true
}

def feature3() {
println "SkipSetupTestA -> feature3"
expect: true
}

@SkipSetup
def feature4() {
println "SkipSetupTestA -> feature4"
expect: true
}
}

package de.scrum_master.testing

import spock.lang.Specification

class SkipSetupTestB extends Specification {
def setup() {
println "SkipSetupTestB -> setup"
}

def feature1() {
println "SkipSetupTestB -> feature1"
expect: true
}

@SkipSetup
def feature2() {
println "SkipSetupTestB -> feature2"
expect: true
}

def feature3() {
println "SkipSetupTestB -> feature3"
expect: true
}

@SkipSetup
def feature4() {
println "SkipSetupTestB -> feature4"
expect: true
}
}

I am also attaching a ZIP file with the source code for your convenience. 😃

Let us complete the picture with the console log output when running both specifications together:


SkipSetupTestA -> setup
SkipSetupTestA -> feature1
SkipSetupTestA -> feature2
SkipSetupTestA -> setup
SkipSetupTestA -> feature3
SkipSetupTestA -> feature4
SkipSetupTestB -> setup
SkipSetupTestB -> feature1
SkipSetupTestB -> feature2
SkipSetupTestB -> setup
SkipSetupTestB -> feature3
SkipSetupTestB -> feature4

I think this is what you wanted, is it not? 
 
Regards
--
Alexander Kriegisch
https://scrum-master.de
SkipSetup.zip

jc

unread,
May 3, 2020, 12:33:57 PM5/3/20
to Geb User Mailing List
I will have to give this a shot. I appreciate the effort you put into this. Also I have to say you have been much more kind and helpful to the people here over the last few months. It's really great to see the change and collaboration and help you can provide.

Marcin Erdmann

unread,
May 10, 2020, 1:22:54 PM5/10/20
to geb-...@googlegroups.com
Thanks for sharing, Alexander. It's cool to see how powerful Spock's extension mechanism is and that it actually allows you to come up with a mechanism to skip certain lifecycle methods based on annotations.

FWIW, your solution could probably be slightly improved by changing the extension to be a global one with the following implementation of IGlobalExtension.visitSpec():

    void visitSpec(SpecInfo spec) {
        def featuresWithSkippedSetup = spec.allFeatures.findAll { it.getAnnotation(SkipSetup) }
        if (featuresWithSkippedSetup) {
            def interceptor = new SkipSetupMethodInterceptor(skippedFeatures: featuresWithSkippedSetup*.name)
            spec.addSetupInterceptor(interceptor)
        }
    }

Marcin


Alexander Kriegisch

unread,
May 10, 2020, 10:48:27 PM5/10/20
to geb-...@googlegroups.com
Thanks Marcin,

but this does not work. In Spock 1.3 (Groovy 2.5) each test dies with
exceptions because in class FeatureInfo we have

@Override
public AnnotatedElement getReflection() {
throw new UnsupportedOperationException("getReflection");
}

Same in 2.0-M2 (Groovy 3.0), only there the test runner does not print
anything and for each test just says that it does not find any tests to
run.

Best regards
--
Alexander Kriegisch


Marcin Erdmann schrieb am 11.05.2020 00:22 (GMT +07:00):
>
> Thanks for sharing, Alexander. It's cool to see how powerful Spock's
> extension mechanism is and that it actually allows you to come up with a
> mechanism to skip certain lifecycle methods based on annotations.
>
>
> FWIW, your solution could probably be slightly improved by changing the
> extension to be a global one with the following implementation of
> IGlobalExtension.visitSpec():
>
> void visitSpec(SpecInfo spec) {
> def featuresWithSkippedSetup = spec.allFeatures.findAll {
> it.getAnnotation(SkipSetup) }
> if (featuresWithSkippedSetup) {
> def interceptor = new SkipSetupMethodInterceptor(skippedFeatures:
> featuresWithSkippedSetup*.name)
> spec.addSetupInterceptor(interceptor)
> }
> }
>
> Marcin
>
>
> On Sun, May 3, 2020 at 5:22 PM Alexander Kriegisch
> <alex...@kriegisch.name <mailto:alex...@kriegisch.name>
> > wrote:
>
>>
>> Hi Jeremy.
>>
>> I agree with Marcin in everything he says. You really should refactor the
>> way he suggested. And as he also said the first time he answered your
>> question, this is a Spock rather than a Geb question.
>>
>> But for what it is worth, I was a little bit bored just now and wanted to
>> find out if an annotation-driven extension
>> <http://spockframework.org/spock/docs/2.0-M2/all_in_one.html#_annotation_driven_local_extensions>
>> ----------------------------------------
>> package de.scrum_master.testing
>>
>> import org.spockframework.runtime.extension.ExtensionAnnotation
>>
>> import java.lang.annotation.Retention
>> import java.lang.annotation.Target
>>
>> import static java.lang.annotation.ElementType.METHOD
>> import static java.lang.annotation.RetentionPolicy.RUNTIME
>>
>> @Retention(RUNTIME)
>> @Target(METHOD)
>> @ExtensionAnnotation(SkipSetupExtension)
>> @interface SkipSetup {}
>> ----------------------------------------
>> package de.scrum_master.testing
>>
>> import
>> org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
>> import org.spockframework.runtime.model.FeatureInfo
>>
>> class SkipSetupExtension extends
>> AbstractAnnotationDrivenExtension<SkipSetup> {
>> SkipSetupMethodInterceptor interceptor
>>
>> @Override
>> void visitFeatureAnnotation(SkipSetup annotation, FeatureInfo feature) {
>> if (!interceptor) {
>> interceptor = new SkipSetupMethodInterceptor()
>> feature.spec.addSetupInterceptor interceptor
>> }
>> interceptor.skippedFeatures << feature.name
>> <http://feature.name>
>> }
>> }
>> ----------------------------------------
>> package de.scrum_master.testing
>> import org.spockframework.runtime.extension.AbstractMethodInterceptor
>> import org.spockframework.runtime.extension.IMethodInvocation
>>
>> class SkipSetupMethodInterceptor extends AbstractMethodInterceptor {
>> List<String> skippedFeatures = new LinkedList<>()
>>
>> @Override
>> void interceptSetupMethod(IMethodInvocation invocation) throws Throwable
>> {
>> if (!skippedFeatures.contains(invocation.feature.name
>> <http://invocation.feature.name> ))
>> invocation.proceed()
>> }
>> }
>> ----------------------------------------
>> package de.scrum_master.testing
>>
>> import spock.lang.Specification
>>
>> class SkipSetupTestA extends Specification {
>> def setup() {
>> println "SkipSetupTestA -> setup"
>> }
>>
>> def feature1() {
>> println "SkipSetupTestA -> feature1"
>> expect: true
>> }
>>
>> @SkipSetup
>> def feature2() {
>> println "SkipSetupTestA -> feature2"
>> expect: true
>> }
>>
>> def feature3() {
>> println "SkipSetupTestA -> feature3"
>> expect: true
>> }
>>
>> @SkipSetup
>> def feature4() {
>> println "SkipSetupTestA -> feature4"
>> expect: true
>> }
>> }
>> ----------------------------------------
>> package de.scrum_master.testing
>>
>> import spock.lang.Specification
>>
>> class SkipSetupTestB extends Specification {
>> def setup() {
>> println "SkipSetupTestB -> setup"
>> }
>>
>> def feature1() {
>> println "SkipSetupTestB -> feature1"
>> expect: true
>> }
>>
>> @SkipSetup
>> def feature2() {
>> println "SkipSetupTestB -> feature2"
>> expect: true
>> }
>>
>> def feature3() {
>> println "SkipSetupTestB -> feature3"
>> expect: true
>> }
>>
>> @SkipSetup
>> def feature4() {
>> println "SkipSetupTestB -> feature4"
>> expect: true
>> }
>> }
>> ----------------------------------------
>>
>> I am also attaching a ZIP file with the source code for your convenience.
>> 😃
>>
>> Let us complete the picture with the console log output when running both
>> specifications together:
>> ----------------------------------------
>> SkipSetupTestA -> setup
>> SkipSetupTestA -> feature1
>> SkipSetupTestA -> feature2
>> SkipSetupTestA -> setup
>> SkipSetupTestA -> feature3
>> SkipSetupTestA -> feature4
>> SkipSetupTestB -> setup
>> SkipSetupTestB -> feature1
>> SkipSetupTestB -> feature2
>> SkipSetupTestB -> setup
>> SkipSetupTestB -> feature3
>> SkipSetupTestB -> feature4
>>
>> ----------------------------------------
>>>> <mailto:geb-user+u...@googlegroups.com> .
>>>> <https://groups.google.com/d/msgid/geb-user/306afc9f-0883-4a40-b16c-e786dbfde9a4%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "Geb User Mailing List" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to geb-user+u...@googlegroups.com
>>> <mailto:geb-user+u...@googlegroups.com> .
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/geb-user/CA%2B52dQQt_i-1EESWL9fjO3ZMB5FwFXCaeFKb46nn4eg_R2oG%2Bw%40mail.gmail.com
>>> <https://groups.google.com/d/msgid/geb-user/CA%2B52dQQt_i-1EESWL9fjO3ZMB5FwFXCaeFKb46nn4eg_R2oG%2Bw%40mail.gmail.com?utm_medium=email&utm_source=footer>
>>> .
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "Geb User Mailing List" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to geb-user+u...@googlegroups.com
>> <mailto:geb-user+u...@googlegroups.com> .
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/geb-user/20200503162255.48EAC44C06BC%40dd39516.kasserver.com
>> <https://groups.google.com/d/msgid/geb-user/20200503162255.48EAC44C06BC%40dd39516.kasserver.com?utm_medium=email&utm_source=footer>
>> .
>
> --
> You received this message because you are subscribed to the Google Groups
> "Geb User Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to geb-user+u...@googlegroups.com
> <mailto:geb-user+u...@googlegroups.com> .
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/geb-user/CA%2B52dQRzDpeqWzOpN-k2o%3DE_dhBGUAQRDZKw%2By3hkB_oHK1eCA%40mail.gmail.com
> <https://groups.google.com/d/msgid/geb-user/CA%2B52dQRzDpeqWzOpN-k2o%3DE_dhBGUAQRDZKw%2By3hkB_oHK1eCA%40mail.gmail.com?utm_medium=email&utm_source=footer>
> .

jc

unread,
May 11, 2020, 9:02:25 AM5/11/20
to Geb User Mailing List
Hi Alexander,

Just wanted to report back that this is working great.  Implemented it and got it going!  I understand your concerns about the structure of the specs but we do have some edge cases where this makes sense for us.  I appreciate the work you put into doing it as I don't think I ever would have figured that out!
To unsubscribe from this group and stop receiving emails from it, send an email to geb-...@googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "Geb User Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to geb-...@googlegroups.com.

Marcin Erdmann

unread,
May 13, 2020, 3:52:47 PM5/13/20
to geb-...@googlegroups.com
Yes, you are right, Alexander. I posted my previous solution without trying it out. FWIW, I got it working this way, I think it's a simpler solution than using an annotation driven extension:

class SkipSetupExtension extends AbstractGlobalExtension {
    @Override
    void visitSpec(SpecInfo spec) {
        spec.addSetupInterceptor(new SkipSetupMethodInterceptor())
    }
}

class SkipSetupMethodInterceptor implements IMethodInterceptor {
    @Override
    void intercept(IMethodInvocation invocation) throws Throwable {
        if (!invocation.feature.featureMethod.getAnnotation(SkipSetup)) {
            invocation.proceed()
        }
    }
}

@Retention(RUNTIME)
@Target(METHOD)
@interface SkipSetup {}


Cheers,
Marcin

To unsubscribe from this group and stop receiving emails from it, send an email to geb-user+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/geb-user/20200511024825.82D3144C099E%40dd39516.kasserver.com.

Alexander Kriegisch

unread,
May 14, 2020, 9:45:43 AM5/14/20
to geb-...@googlegroups.com

Definitely simpler, yes. Thanks for this wonderfully elegant solution. :-)

For the benefit of everyone reading this who forgot to check the Spock manual's chapter about global extensions: You will also need a file META-INF/services/org.spockframework.runtime.extension.IGlobalExtension with this content:

de.scrum_master.testing.extension.SkipSetupGlobalExtension
Just one line per global extension.

--
Alexander Kriegisch
https://scrum-master.de

jc

unread,
May 14, 2020, 9:51:35 AM5/14/20
to Geb User Mailing List
I'm confused on how to implement this solution.  Could you provide a short example?
>>>> an email to geb-...@googlegroups.com
>>>> <mailto:geb-...@googlegroups.com> .

>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/geb-user/306afc9f-0883-4a40-b16c-e786dbfde9a4%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/geb-user/306afc9f-0883-4a40-b16c-e786dbfde9a4%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "Geb User Mailing List" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to geb-...@googlegroups.com
>>> <mailto:geb-...@googlegroups.com> .

>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/geb-user/CA%2B52dQQt_i-1EESWL9fjO3ZMB5FwFXCaeFKb46nn4eg_R2oG%2Bw%40mail.gmail.com
>>> <https://groups.google.com/d/msgid/geb-user/CA%2B52dQQt_i-1EESWL9fjO3ZMB5FwFXCaeFKb46nn4eg_R2oG%2Bw%40mail.gmail.com?utm_medium=email&utm_source=footer>
>>> .
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "Geb User Mailing List" group.
>> To unsubscribe from this group and stop receiving emails from it, send an

>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/geb-user/20200503162255.48EAC44C06BC%40dd39516.kasserver.com
>> <https://groups.google.com/d/msgid/geb-user/20200503162255.48EAC44C06BC%40dd39516.kasserver.com?utm_medium=email&utm_source=footer>
>> .
>
> --
> You received this message because you are subscribed to the Google Groups
> "Geb User Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an
To unsubscribe from this group and stop receiving emails from it, send an email to geb-...@googlegroups.com.

Marcin Erdmann

unread,
May 14, 2020, 1:33:25 PM5/14/20
to geb-...@googlegroups.com
All you need to do is add the 3 classes I pasted to your project, annotate the tests for which you want to skip setup with `@SkipSetup` and add a META-INF/services/org.spockframework.runtime.extension.IGlobalExtension resource file as explained by Alexander in his recent email. I don't know what kind of example you are after, sorry.

To unsubscribe from this group and stop receiving emails from it, send an email to geb-user+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/geb-user/56088b0c-0999-45ab-9529-63609e593592%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages