Grails 3:集成测试的自定义应用程序类

时间:2015-04-15 13:52:10

标签: grails grails-3.0

我想自定义用于集成测试的Application类。根据用户指南,这应该是可能的:

  

Integration annotation支持可选的applicationClass属性,该属性可用于指定用于功能测试的应用程序类。该类必须扩展GrailsAutoConfiguration。

(来自http://grails.github.io/grails-doc/3.0.x/guide/testing.html#integrationTesting

所以我的集成测试用

注释
@Integration(applicationClass = TestApplication)
class DataServiceSpec extends Specification {

测试应用程序类(尚未定制)如下所示:

class TestApplication extends GrailsAutoConfiguration {
}

运行集成测试(使用grails test-app或gradle integrationTest会导致ApplicationContextException导致缺少EmbeddedServletContainerFactory的根本原因。 这是一个错误,还是我错误地使用了applicationClass属性?这样的定制应用程序类应该驻留在哪里?当我把它放在集成测试源和grails-app / init中时,我得到了同样的错误。 或者是否有另一种方法可以将另一个@Configuration类添加到集成测试上下文中?

这是完整的堆栈跟踪:

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

2 个答案:

答案 0 :(得分:0)

有时在另一个控制台上运行Grails run-app会导致问题。当我在关闭服务器后运行grails test-app时。它运行没有错误。

答案 1 :(得分:0)

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
}