3

... specifically in Groovy (hence tag)?

In Java you can't do this... but in dynamic languages (e.g. Python) you typically can.

An attempt to do something like in the given block of a Spock feature (i.e. test method) meets with, in Eclipse:

Groovy:Class definition not expected here. Please define the class at an appropriate place or perhaps try using a block/Closure instead.

... an "appropriate" place would obviously be outside the feature. This would be clunky and not groovy. Having used Groovy for a few months now I get a feel for when Groovy should offer something groovier.

So say I'd like to extend my abstract class AbstractFoo and make a new subclass Foo, in my feature, is there any way to "use a block/Closure" to achieve something like that?

mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • Possible duplicate of: https://stackoverflow.com/questions/23160278/create-a-groovy-class-dynamically – Michael Easter Jun 09 '18 at 11:29
  • @MichaelEaster Don't think that flies. The *titles* make them seem the same. But have a look at the two questions and tell me whether they are the same. I'm looking for a way specifically to create a *subclass*, and not by using some gigantic mechanism. My question was in fact inspired by the quoted Eclipse message. If the latter merely means what Szymon has put forward that's a bit disappointing... – mike rodent Jun 09 '18 at 16:07

1 Answers1

6

You can simply create an anonymous class by instantiating AbstractFoo and providing inline implementation of abstract methods. Consider following example:

abstract class AbstractFoo {
    void bar() {
        println text()
    }

    abstract String text()
}

def foo1 = new AbstractFoo() {
    @Override
    String text() {
        return "Hello, world!"
    }
}

def foo2 = new AbstractFoo() {
    @Override
    String text() {
        return "Lorem ipsum dolor sit amet"
    }
}

foo1.bar()
foo2.bar()

Both foo1 and foo2 implement AbstractFoo and they provide different implementation of text() method that results in different bar() method behavior. Running this Groovy script produces following output to the console:

Hello, world!
Lorem ipsum dolor sit amet

It's nothing Groovy-specific, you can achieve exactly the same behavior with Java. However you can make it a little bit more "groovier" by casting a closure to a AbstractFoo class, something like this:

def foo3 = { "test 123" } as AbstractFoo
foo3.bar()

In this case closure that returns "test 123" provides an implementation for an abstract text() method. It works like that if your abstract class has only single abstract method.

Abstract class with multiple abstract methods

But what happens if an abstract class has multiple abstract methods we want to implement on fly? In this case we can provide implementation of this methods as a map, where keys are names of abstract methods and values are closures providing implementation. Let's take a look at following example:

abstract class AbstractFoo {
    abstract String text()
    abstract int number()
    void bar() {
        println "text: ${text()}, number: ${number()}"
    }
}

def foo = [
    text: { "test 1" },
    number: { 23 }
] as AbstractFoo

foo.bar()

This example uses an abstract class with two abstract methods. We can instantiate this class by casting a map of type Map<String, Closure<?>> to AbstractFoo class. Running this example produces following output to the console:

text: test 1, number: 23

Creating non-anonymous classes on fly in Groovy

Groovy also allows you to create a class e.g. from a multiline string using GroovyClassLoader.parseClass(input) method. Let's take a look at following example:

abstract class AbstractFoo {
    void bar() {
        println text()
    }

    abstract String text()
}

def newClassDefinitionAsString = '''
class Foo extends AbstractFoo {
    String text() {
        return "test"
    }
}
'''

def clazz = new GroovyClassLoader(getClass().getClassLoader()).parseClass(newClassDefinitionAsString)

def foo = ((AbstractFoo) clazz.newInstance())

foo.bar()

Here we are defining a non-anonymous class called Foo that extends AbstractFoo and provides a definition of test() method. This approach is pretty error prone, because you define a new class as String, so forget about any IDE support in catching errors and warnings.

Providing a subclass in a test specification

Your initial question mentioned about an attempt to create a class for a specification in a given: Spock block. I would strongly suggest using the simplest available tool - creating a nested private static class so you can easily access it inside your test and you don't expose it outside the test. Something like this:

class MySpec extends Specification {

    def "should do something"() {
        given:
        Class<?> clazz = Foo.class

        when:
        //....

        then:
        ///....
    }

    private static class Foo extends AbstractFoo  {

    }
}
Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • Thanks. The `... as AbstractFoo` stuff is new to me, but everything else I knew. None of the above allows the creation/definition of a (non-anonymous) **new class**, though, only new objects/instances. – mike rodent Jun 09 '18 at 08:40
  • @mikerodent Got it. Why not using a nested private static class then? It's easiest way and less error prone comparing to generating Groovy classes dynamically at runtime (added an example of that as well) – Szymon Stepniak Jun 09 '18 at 09:21
  • Yes, this is what I am currently doing. Although there's no particular need for `static`, and no particular point in `private`: in Groovy the only effect of `private` is to hide the field/method/inner class from a subclass. – mike rodent Jun 09 '18 at 09:31
  • Thanks for your efforts... looks like `{ ... } as AbstractFoo` is as good as it gets (unless some uber-geek has a better idea...) – mike rodent Jun 10 '18 at 19:49