Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attribute ServerContainer not found in ServletContext

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!

like image 471
Sunchezz Avatar asked Jan 02 '23 00:01

Sunchezz


1 Answers

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 :D

Hope this also helps someone else, to save hours of searching, pain and googling ;) :)

like image 200
Sunchezz Avatar answered Jan 04 '23 16:01

Sunchezz