In my Spring boot application i use also websockets. Everything works fine, as expected in production.
Now i started to create UnitTests with Spring-Boot-Test.
Every time i start a @SpringBootTest , i get following exception (shortened):
java.lang.IllegalStateException: Failed to load ApplicationContext Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'createWebSocketContainer' defined in class path resource [com/package/spring/ws/MyWebsocketConfig.class]: Invocation of init method failed; Caused by: java.lang.IllegalStateException: Attribute 'javax.websocket.server.ServerContainer' not found in ServletContext
Long Version
java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'createWebSocketContainer' defined in class path resource [com/package/spring/ws/MyWebsocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Attribute 'javax.websocket.server.ServerContainer' not found in ServletContext at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:830) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) ... 24 more Caused by: java.lang.IllegalStateException: Attribute 'javax.websocket.server.ServerContainer' not found in ServletContext at org.springframework.util.Assert.state(Assert.java:73) at org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean.afterPropertiesSet(ServletServerContainerFactoryBean.java:117) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774) ... 39 more
Spring Websocket Config Class
package com.package.spring.ws; // removed imports @Configuration @EnableWebSocketMessageBroker public class MyWebsocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> { private final Logger logger = LogManager.getLogger(MyWebsocketConfig.class); private int MAX_MESSAGE_SIZE = 1024 * 1024 * 16; @Override protected void configureStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/wss") .setAllowedOrigins("*") .withSockJS(); } @Override public void configureWebSocketTransport(WebSocketTransportRegistration registration) { registration.setMessageSizeLimit(MAX_MESSAGE_SIZE); registration.setSendBufferSizeLimit(MAX_MESSAGE_SIZE); registration.setSendTimeLimit(20000); super.configureWebSocketTransport(registration); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.setApplicationDestinationPrefixes("/app"); config.enableSimpleBroker("/queue", "/events", "/topic"); config.setCacheLimit(1024 * 10); super.configureMessageBroker(config); } // This is breaking my Spring Unit Tests. @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE); container.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE); return container; } }
POM.XML
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.package</groupId> <artifactId>MyProject</artifactId> <version>0.8.4-RELEASE</version> <packaging>war</packaging> <name>My Project</name> <description>MyProject Server</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- Problem exists, with and without this websocket dependency --> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-messaging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Spring Application Class
package com.packge.spring; // removed imports @ComponentScan({"com.package.spring", "com.packe.spring.security", "com.package.endpoints"}) @EnableJpaRepositories(basePackages = "com.package.db") @EntityScan(value = "com.package.db") @SpringBootApplication public class MyApplication extends SpringBootServletInitializer { @Value("my.session.cookie-name") private String cookieName = ApplicationConfig.SESSION_COOKIE_NAME; public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
Typical Failing Test
@RunWith(SpringRunner.class) @SpringBootTest(classes = MyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class LedgerTreeMovementsTest { @Test public void test0_testXYZ() { } }
Problem
The Problem is, the actual Test never comes to the test methods, its breaking on setting up the Test application. Does anybody has an idea, whats going on? Any further infos needed? Thx a lot!
Advertisement
Answer
Thx to GSSwain, who lead me to the right direction, i now was able to solve my problem.
It only seem to apear if the @SpringBootTest
attribute webEnviroment
is set to MOCK
, or NONE
. Using a defined, or random port tells Spring to load a proper ServletContext.
There are multiple ways to get around this, if you dont want to load a full Context for simple tests.
Solution 1
This is what most people will need. Add an additional @WebAppConfiguration
annotation to your test classes, which tells Spring to load a proper ServletContext with a mocked attribute entry for the ServerContainer.
Solution 2
For my needs, this was still to much background inflating, things i dont need, in many Testcases. So i added a check in the Bean creation itself and added a property to switch the check on or off, via application.properties.
In your WebsocketConfig class:
@Autowired private ServletContext servletContext; private boolean ignoreNullWsContainer; @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { // Check if null-container is allowed to prevent Exceptions if (ignoreNullWsContainer) { // Check if attribute is set in the ServletContext ServerContainer serverContainer = (ServerContainer) this.servletContext.getAttribute("javax.websocket.server.ServerContainer"); if (serverContainer == null) { logger.error("Could not initialize Websocket Container in Testcase."); return null; } } ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE); container.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE); return container; } @Value("${project.ignore-null-websocket-container:false}") private void setIgnoreNullWsContainer(String flag) { this.ignoreNullWsContainer = Boolean.parseBoolean(flag); }
And in your application.properties in your Test src resources directory (default dir src/test/resources/application.properties
project.ignoreNullWsContainer = true
This way, if you dont set this property in the your production enviroment property file, Spring will still fail if there is something wrong with the ServerContainer.
Thats what i took in the end, because i didn’t need the Spring test features, i only needed spring to pick up my datasource configuration (in properties file) and executing my @Sql annotations 😀
Hope this also helps someone else, to save hours of searching, pain and googling 😉 🙂