Micronaut Service with Groovy & Spock
Micronaut supports Java, Groovy and Kotlin languages. In this post let's explore creating a service using Groovy language and Spock testing framework.
If you don't have the Micronaut CLI installed, this post has the details of how to get it installed.
1.1.0 is the latest version of Micronaut as of writing this post.
Launch the CLI using mn
command.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/Users/naresha/.sdkman/candidates/micronaut/1.1.0/cli-1.1.0.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Resolving dependencies....
| Starting interactive mode...
| Enter a command name to run. Use TAB for completion:
mn>
If you are using JDK versions 9 or newer, you might see those reflective access warnings. These should get fixed when Groovy 3 is released and used in Micronaut (as per https://issues.apache.org/jira/browse/GROOVY-8843). For now, you can either live with the warnings or use JDK 8. SDKMAN makes it easy to switch between the Java versions.
Let's create a service using the create-app
command.
mn> create-app --lang groovy com.nareshak.groovy-sample
| Generating Groovy project...
| Application created at /Users/naresha/practice/micronaut/ga/1dot1/groovy-sample
| Initializing application. Please wait...
Let's cd into the project directory and run the create-controller
command from the CLI.
mn> create-controller Hello
| Rendered template Controller.groovy to destination src/main/groovy/com/nareshak/HelloController.groovy
| Rendered template ControllerSpec.groovy to destination src/test/groovy/com/nareshak/HelloControllerSpec.groovy
Let's do a quick comparison of the dependencies of a java service with the dependencies of a groovy service. Below are the dependencies of a java service
dependencies {
annotationProcessor "io.micronaut:micronaut-inject-java"
annotationProcessor "io.micronaut:micronaut-validation"
compile "io.micronaut:micronaut-inject"
compile "io.micronaut:micronaut-validation"
compile "io.micronaut:micronaut-runtime"
compile "io.micronaut:micronaut-http-client"
compile "javax.annotation:javax.annotation-api"
compile "io.micronaut:micronaut-http-server-netty"
runtime "ch.qos.logback:logback-classic:1.2.3"
testAnnotationProcessor "io.micronaut:micronaut-inject-java"
testCompile "org.junit.jupiter:junit-jupiter-api"
testCompile "io.micronaut.test:micronaut-test-junit5"
testRuntime "org.junit.jupiter:junit-jupiter-engine"
}
Dependencies of a groovy service look as follows.
dependencies {
compile "io.micronaut:micronaut-runtime-groovy"
compile "io.micronaut:micronaut-validation"
compile "io.micronaut:micronaut-http-client"
compile "javax.annotation:javax.annotation-api"
compile "io.micronaut:micronaut-http-server-netty"
compileOnly "io.micronaut:micronaut-inject-groovy"
runtime "ch.qos.logback:logback-classic:1.2.3"
testCompile("org.spockframework:spock-core") {
exclude group: "org.codehaus.groovy", module: "groovy-all"
}
testCompile "io.micronaut:micronaut-inject-groovy"
testCompile "io.micronaut.test:micronaut-test-spock:1.0.1"
}
While Java projects make use of annotation processors for dependency injection, groovy has support for AST transformations, which has been leveraged here. That's why we have the dependency 'micronaut-inject-groovy'. Also there is a Spock dependency replacing the JUnit. There is also 'micronaut-test-spock', which supplies the extensions for Spock (to handle @MicronautTest).
Now let's take a look at the generated controller class.
package com.nareshak
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.HttpStatus
@Controller("/hello")
class HelloController {
@Get("/")
HttpStatus index() {
return HttpStatus.OK
}
}
As a convention in Groovy, 'public' keyword is omitted here, which is the only difference compared to a controller in Java language. If you do not intend to use the dynamic features of Groovy, you could apply the '@CompileStatic' AST transformation.
Now let's move on to the the main class - Application.groovy.
package com.nareshak
import io.micronaut.runtime.Micronaut
import groovy.transform.CompileStatic
@CompileStatic
class Application {
static void main(String[] args) {
Micronaut.run(Application)
}
}
Here the '@CompileStatic' transformation is already applied.
Finally, let's see the Spock specification class.
package com.nareshak
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.annotation.MicronautTest
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import javax.inject.Inject
@MicronautTest
class HelloControllerSpec extends Specification {
@Inject
EmbeddedServer embeddedServer
@Shared @AutoCleanup
RxHttpClient client
void setup() {
client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())
}
void "test index"() {
given:
HttpResponse response = client.toBlocking().exchange("/hello")
expect:
response.status == HttpStatus.OK
}
}
@MicronautTest
will do all magic required to setup an embedded server, which will be injected into the field embeddedServer
. Then we create a client, using which we can invoke the endpoint. client
is marked as @Shared
and @AutoCleanup
. @Shared
is used in Spock to reuse the field across multiple tests (or put in another way, to avoid recreating a costly resource before every test case). @AutoCleanup
will ensure resource is closed after use, typically by invoking the close
method on it.
There is a contradiction here. client
is initialised in the setup()
method, which is run before every test, but @Shared
is meant to be reused across tests. Going by what we saw in the generated JUnit test from this post, where client is initialised in every test, the correct/ consistent approach seems to be to remove @Shared
. I have reported it here.
Alternatively, client can be marked as @Shared
and initialised in setupSepc()
method. But this will force us to make embeddedServer
as shared (setupSpec can access only shared fields). When I tried this, I saw @Inject
on embeddedServer
did not work.
Spock provides structuring your test code into given-when-then/expect format, which I find it very useful.
Personally my recommendation would be to use Spock for testing, even if you intend to write your production code in Java.