Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails 3: Custom Application class for integration tests

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
like image 651
rainerfrey Avatar asked Oct 31 '22 06:10

rainerfrey


1 Answers

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 own EmbeddedServletContainerFactory bean).

Short Answer

Add the missing annotations so that your TestApplication class mirrors the one above.

Long Answer

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 single EmbeddedServletContainerFactory bean within the ApplicationContext 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
}
like image 72
dpcasady Avatar answered Nov 16 '22 15:11

dpcasady