14

I have simple Groovy category class which adds method to String instances:

final class SampleCategory {

    static String withBraces(String self) {
        "($self)"
    }

}

I want to use this category in my unit tests (for example). It looks like this:

class MyTest {
    @Test
    void shouldDoThis() {
        use (SampleCategory) {
            assert 'this'.withBraces() == '(this)'
        }
    }

    @Test
    void shouldDoThat() {
        use (SampleCategory) {
            assert 'that'.withBraces() == '(that)'
        }
    }
}

What I'd like to achieve, however, is ability to specify that category SampleCategory is used in scope of each and every instance method of MyTest so I don't have to specify use(SampleCategory) { ... } in every method.

Is it possible?

oiavorskyi
  • 2,893
  • 1
  • 20
  • 23
  • Should be doable with AST transformations, not sure how to implement it tho. There's a section in the userguide [here](http://groovy.codehaus.org/Compile-time+Metaprogramming+-+AST+Transformations) and Hamlet D'arcy talks about it in his ["Code generation on the JVM"](http://hamletdarcy.blogspot.com/2010/07/code-generation-on-jvm-video-and-slides.html)-talk (not sure how deep he goes). Good luck! – xlson Apr 05 '11 at 14:15
  • Thank you for idea! Sounds like a good small OSS project candidate :) – oiavorskyi Apr 05 '11 at 15:09

2 Answers2

12

You can use mixin to apply the category directly to String's metaClass. Assign null to the metaClass to reset it to groovy defaults. For example:

@Before void setUp() { 
    String.mixin(SampleCategory)
}

@After void tearDown() {
    String.metaClass = null
}

@Test
void shouldDoThat() {
    assert 'that'.withBraces() == '(that)'
}
ataylor
  • 64,891
  • 24
  • 161
  • 189
  • Did you actually try this out? I came up with an almost identical solution, tested it out, and it didn't work. – Dónal Apr 05 '11 at 17:58
  • ataylor, thank you, solution really works and it's quite elegant! I made couple of minor changes (e.g. use of @BeforeClass/@AfterClass) – oiavorskyi Apr 05 '11 at 18:22
  • Great, I figured there would be an eaier way than doing custom AST programming :) Thanks for the tip! – xlson Apr 06 '11 at 07:16
2

Now you have the option to use extension modules instead of categories: http://mrhaki.blogspot.se/2013/01/groovy-goodness-adding-extra-methods.html

On the plus side Intellij will recognize the extensions. I've just noticed that it doesn't even need to be a separate module as suggested by the link, just add META-INF/services/org.codehaus.groovy.runtime.ExtensionModule to the project:

# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = module
moduleVersion = 1.0
extensionClasses = SampleExtension

The extension class is pretty much defined like a normal category:

class SampleExtension {
    static String withBraces(String self) {
        "($self)"
    }
}

Can be used like:

def "Sample extension"() {
    expect: 'this'.withBraces() == '(this)'
}

If you are using Spock there is a @Use annotation that can be used on the specifications. The drawback with that is that Intellij will not recognize it.

Love
  • 1,709
  • 2
  • 22
  • 30