I want to customize the Application class that's used for integration tests. According to the user guide, this should be possible:
The Integration annotation supports an optional applicationClass attribute which may be used to specify the application class to use for the functional test. The class must extend GrailsAutoConfiguration.
(from http://grails.github.io/grails-doc/3.0.x/guide/testing.html#integrationTesting)
So my integration test is annotated with
@Integration(applicationClass = TestApplication)
class DataServiceSpec extends Specification {
The test application class (not yet customized) looks like:
class TestApplication extends GrailsAutoConfiguration {
}
Running the integration test (with either grails test-app or gradle integrationTest results in an ApplicationContextException with root cause of a missing EmbeddedServletContainerFactory. Is this a bug, or do I use the applicationClass attribute wrongly? Where should such a customized application class reside? I got the same error when I put it within the integration-test sources and within grails-app/init. Or is there another way to add another @Configuration class to the integration test context?
Here's the full stack trace:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:94)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:72)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:212)
at org.spockframework.spring.SpringTestContextManager.prepareTestInstance(SpringTestContextManager.java:49)
at org.spockframework.spring.SpringInterceptor.interceptSetupMethod(SpringInterceptor.java:42)
at org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:28)
at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:87)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:64)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:106)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.context.ApplicationContextException: Unable to start embedded container; nested exception is org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:474)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at grails.boot.GrailsApp.run(GrailsApp.groovy:49)
at org.springframework.boot.test.SpringApplicationContextLoader.loadContext(SpringApplicationContextLoader.java:101)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86)
... 24 more
Caused by: org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.getEmbeddedServletContainerFactory(EmbeddedWebApplicationContext.java:183)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(EmbeddedWebApplicationContext.java:156)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:130)
... 32 more
Unfortunately, creating a class that extends GrailsAutoConfiguration
isn't enough. For the default Grails Application
class, some AST transformations take place behind the scenes to provide everything necessary for the app to run. When all is said and done, the Application class really looks more like this:
@EnableWebMvc
@EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration, MessageSourceAutoConfiguration, ReactorAutoConfiguration])
public class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
}
The @EnableAutoConfiguration
annotation is what really makes things work. The Spring Boot docs describe what it does:
Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined. For example, If you have tomcat-embedded.jar on your classpath you are likely to want a
TomcatEmbeddedServletContainerFactory
(unless you have defined your ownEmbeddedServletContainerFactory
bean).
Add the missing annotations so that your TestApplication
class mirrors the one above.
Grails 3 apps are, at their core, Spring Boot apps. Likewise, the main method that the default Grails Application
class exposes is responsible for running the app which it does by invoking GrailsApp.run()
. GrailsApp
extends SpringApplication
which is responsible for the heavy lifting of running the Spring Boot app.
Part of what SpringApplication
is responsible for is creating the Spring application context. By default, Spring Boot creates an AnnotationConfigEmbeddedWebApplicationContext
. As stated in the Spring Boot documentation:
This context will create, initialize and run an
EmbeddedServletContainer
by searching for a singleEmbeddedServletContainerFactory
bean within theApplicationContext
itself.
So obviously, for this to work, there needs to be an EmbeddedServletContainerFactory
defined somewhere. The exception that you're seeing is due to the fact that none is found. We have two options here. You can either do what Grails does to the default Application class and add an @EnableAutoConfiguration
annotation as shown above, or explicitly define your own EmbeddedServletContainerFactory
:
@Configuration
class TestApplication extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(TestApplication, args)
}
@Bean
public EmbeddedServletContainerFactory containerFactory() {
return new TomcatEmbeddedServletContainerFactory(0)
}
}
Note that Grails only scans classes relative to the Application class by default. You'll probably need to override that by adding the following to your Application
class:
@Override
protected boolean limitScanningToApplication() {
return false
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With