Reputation: 15652
... 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?
Upvotes: 3
Views: 2012
Reputation: 42194
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.
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
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.
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 {
}
}
Upvotes: 6