How to disable autoconfiguration for undertow - java

I have a normal spring-boot web application using spring-boot-starter-web, i.e. an embedded tomcat.
Now one of the libraries I'm using for testing comes with undertow as a dependency (because it itself is starting an embedded webserver for mocking an external dependency), and this seems to get the spring-boot autoconfiguration to try to configure undertow as the embedded web server (which seems to break due to version mismatches, and is also not what I want – I'm fine with tomcat as my server).
Here is our test class:
package org.zalando.nakadiproducer.tests;
[... imports skipped ...]
import static io.restassured.RestAssured.given;
#RunWith(SpringRunner.class)
#SpringBootTest(
// This line looks like that by intention: We want to test that the MockNakadiPublishingClient will be picked up
// by our starter *even if* it has been defined *after* the application itself. This has been a problem until
// this commit.
classes = { Application.class, MockNakadiConfig.class },
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
//#EnableAutoConfiguration(exclude=EmbeddedWebServerFactoryCustomizerAutoConfiguration.class)
public class ApplicationIT {
#LocalManagementPort
private int localManagementPort;
#ClassRule
public static final EnvironmentVariables environmentVariables
= new EnvironmentVariables();
#BeforeClass
public static void fakeCredentialsDir() {
environmentVariables.set("CREDENTIALS_DIR", new File("src/main/test/tokens").getAbsolutePath());
}
#Test
public void shouldSuccessfullyStartAndSnapshotCanBeTriggered() {
given().baseUri("http://localhost:" + localManagementPort).contentType("application/json")
.when().post("/actuator/snapshot-event-creation/eventtype")
.then().statusCode(204);
}
}
With the main application class:
package org.zalando.nakadiproducer.tests;
[imports skipped]
#EnableAutoConfiguration
#EnableNakadiProducer
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
#Bean
#Primary
public DataSource dataSource() throws IOException {
return embeddedPostgres().getPostgresDatabase();
}
#Bean
public EmbeddedPostgres embeddedPostgres() throws IOException {
return EmbeddedPostgres.start();
}
#Bean
public SnapshotEventGenerator snapshotEventGenerator() {
return new SimpleSnapshotEventGenerator("eventtype", (withIdGreaterThan, filter) -> {
if (withIdGreaterThan == null) {
return Collections.singletonList(new Snapshot("1", "foo", filter));
} else if (withIdGreaterThan.equals("1")) {
return Collections.singletonList(new Snapshot("2", "foo", filter));
} else {
return new ArrayList<>();
}
});
// Todo: Test that some events arrive at a local nakadi mock
}
}
This is the error message:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'undertowWebServerFactoryCustomizer' defined in class path resource [org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration$UndertowWebServerFactoryCustomizerConfiguration.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer] from ClassLoader [sun.misc.Launcher$AppClassLoader#378fd1ac]
The mentioned definition class is in spring-boot-autoconfigure 2.0.3.RELEASE, and looks like this:
#Configuration
#EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
#ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
// tomcat, jetty
/**
* Nested configuration if Undertow is being used.
*/
#Configuration
#ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
public static class UndertowWebServerFactoryCustomizerConfiguration {
#Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(
Environment environment, ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
How can I tell spring-boot to not configure Undertow?
I tried #EnableAutoConfiguration(exclude=EmbeddedWebServerFactoryCustomizerAutoConfiguration.class) on my test class (beside #SpringBootTest), but that has no effect.
If I try #EnableAutoConfiguration(exclude=EmbeddedWebServerFactoryCustomizerAutoConfiguration.UndertowWebServerFactoryCustomizerConfiguration.class), I get this error:
Caused by: java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
- org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$UndertowWebServerFactoryCustomizerConfiguration

Removing Undertow from your project's dependencies is the safest way. Spring Boot is based on classpath scanning so once Undertow is gone from the classpath it's auto-configuration won't be processed.
The problem with EmbeddedWebServerFactoryCustomizerAutoConfiguration is that it doesn't provide a property switch. It's purely based on servlet container class presence. To get rid of it, you would have to exclude the entire EmbeddedWebServerFactoryCustomizerAutoConfiguration:
#EnableAutoConfiguration(exclude=EmbeddedWebServerFactoryCustomizerAutoConfiguration.class)
public MyTest {
}
and in your test configuration define just the beans for starting Tomcat:
#TestConfiguraton
#EnableConfigurationProperties(ServerProperties.class)
public MyTestConfig {
#Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}

Related

Spring boot Test fails saying, Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean

Test Class:-
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { WebsocketSourceConfiguration.class,
WebSocketSourceIntegrationTests.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
"websocket.path=/some_websocket_path", "websocket.allowedOrigins=*",
"spring.cloud.stream.default-binder=kafka" })
public class WebSocketSourceIntegrationTests {
private String port = "8080";
#Test
public void testWebSocketStreamSource() throws IOException, InterruptedException {
StandardWebSocketClient webSocketClient = new StandardWebSocketClient();
ClientWebSocketContainer clientWebSocketContainer = new ClientWebSocketContainer(webSocketClient,
"ws://localhost:" + port + "/some_websocket_path");
clientWebSocketContainer.start();
WebSocketSession session = clientWebSocketContainer.getSession(null);
session.sendMessage(new TextMessage("foo"));
System.out.println("Done****************************************************");
}
}
I have seen same issue here but nothing helped me. May I know what I'm missing ?
I have spring-boot-starter-tomcat as compile time dependency in the dependency Hierarchy.
This message says:
You need to configure at least 1 ServletWebServerFactory bean in the ApplicationContext, so if you already have spring-boot-starter-tomcat you need to either autoconfigure that bean or to do it manually.
So, in the test there are only 2 configuration classes to load the applicationContext, these are = { WebsocketSourceConfiguration.class, WebSocketSourceIntegrationTests.class }, then at least in one of these classes there should be a #Bean method returning an instance of the desired ServletWebServerFactory.
* SOLUTION *
Make sure to load all the beans within your configuration class
WebsocketSourceConfiguration {
#Bean
ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
}
OR also enable the AutoConfiguration to do a classpath scanning and auto-configuration of those beans.
#EnableAutoConfiguration
WebsocketSourceConfiguration
Can be done also at the Integration Test class.
#EnableAutoConfiguration
WebSocketSourceIntegrationTests
For more information check the SpringBootTest annotation documentation
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html
in 2.0.5.RELEASE i faced a similar issue when I had the following.
package radon;
..
#SpringBootApplication
public class Initializer {
public static void main(String[] args) {
SpringApplication.run(Config.class, args);
}
}
package radon.app.config;
#Configuration
#ComponentScan({ "radon.app" })
public class Config {
..
}
Changing the package of Initializer from radon to radon.app fixed the issue.
this is because spring is not able to load the properties file at runtime, i was using spring profiles and wasn't providing the (program or vm) argument at runtime( java -jar application.jar) , adding vm argument of profile resolved the issue for me.
java -jar -Dspring.profiles.active=dev application.jar
or using program argument
java -jar application.jar --spring.profiles.active=prod --spring.config.location=c:\config
For web applications, extends *SpringBootServletInitializer* in main class.
#SpringBootApplication
public class YourAppliationName extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(YourAppliationName.class, args);
}
}

Running tests in Spring Boot

I have this test class
I've generated a Spring Boot web application using Spring Initializr, using embedded Tomcat + Thymeleaf template engine, and package as an executable JAR file.
Technologies used :
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
my classes:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TdkApplicationTests {
#Test
public void contextLoads() {
}
}
#Configuration
public class PersistenceConfig {
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
#Bean
public IOTEcoSystemManager iOTEcoSystemManager() {
return new IOTEcoSystemManagerImpl();
}
#Bean
public DeviceEventRepository deviceEventRepository() {
return new JdbcDeviceEventRepository();
}
#Bean
public DeviceRepository deviceRepository() {
return new JdbcDeviceRepository();
}
/**
* Creates an in-memory "books" database populated
* with test data for fast testing
*/
#Bean
public DataSource dataSource(){
return
(new EmbeddedDatabaseBuilder())
.addScript("classpath:db/H2.schema.sql")
.addScript("classpath:db/H2.data.sql")
.build();
}
}
#ContextConfiguration(classes={PersistenceConfig.class})
#RunWith(SpringRunner.class)
public class BookManagerTests {
/**
* The object being tested.
*/
#Autowired
BookManager bookManager;
#Test
public void testfindDeviceByKey() {
Device device = bookManager.findDeviceByKey("C380F");
assertNotNull(device);
assertEquals("C380F", device.getDeviceKey());
assertEquals(1, device.getId().longValue());
}
#Test
public void testfindDeviceByKeyFail() {
Device device = null;
try {
device = bookManager.findDeviceByKey("C380FX");
} catch (EmptyResultDataAccessException erdae) {
assertNotNull(erdae);
}
assertNull(device);
}
}
#Service("bookManager")
public class BookManagerImpl implements BookManager {
...
}
If I run all the test of the package I got this error:
unique constraint or index violation; SYS_PK_10092 table: T_COMPANY
because the script runs twice. If I remove
classes={PersistenceConfig.class}
from BookManagerTests I got this dependency problem
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
If I run both test individually everything is OK
PersistenceConfig and its content seem to be created twice: first time as ordinary bean and second time as config (that's right). I recommend to create single main test class with all annotations and then extend your tests (without any annotation) from it.
#ContextConfiguration(classes={PersistenceConfig.class})
#RunWith(SpringRunner.class)
#SpringBootTest
public abstract class MainTest {
}
public BookManagerTest extends MainTest {
...
}
To me it seems the constraint validation happens because PersistentConfig is loaded twice, and that masks your second problem. Where is BookManager defined? Are you sure your scanning the classpath correctly for it, if it's annotated with #repository?

Spring dependency injection into Spring TestExecutionListeners not working

How can I use Spring dependency injection into a TestExecutionListener class I wrote extending AbstractTestExecutionListener?
Spring DI does not seem to work with TestExecutionListener classes.
Example of issue:
The AbstractTestExecutionListener:
class SimpleClassTestListener extends AbstractTestExecutionListener {
#Autowired
protected String simplefield; // does not work simplefield = null
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("simplefield " + simplefield);
}
}
Configuration file:
#Configuration
#ComponentScan(basePackages = { "com.example*" })
class SimpleConfig {
#Bean
public String simpleField() {
return "simpleField";
}
}
The JUnit Test file:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { SimpleConfig.class })
#TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS, listeners = {
SimpleClassTestListener.class })
public class SimpleTest {
#Test
public void test(){
assertTrue();
}
}
As highlighted in the code comment, when I run this, it will print "simplefield null" because simplefield never gets injected with a value.
Just add autowiring for the whole TestExecutionListener.
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
// your code that uses autowired fields
}
Check sample project in github.
In the case of Spring Boot 2 using
estContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this)
was triggering the creation of the Spring context before the #SpringBootTest base class was created. This missed then some critical configuration parameters in my case. I had to use testContext.getApplicationContext().getBean( in beforeTestClass for getting a bean instance.

Spring: How to set system properties in integration test when #ComponentScan is used?

Summary: Adding the #ComponentScan (or #SpringBootApplication) annotation to my application class changes the behaviour of SpringApplicationBuilder.properties() and breaks my integration test.
I am using a cut-down version of the Spring Boot sample:
spring-boot-sample-websocket-jetty
I have removed everything except what is required for the "echo" example (and I'm using Spring Boot 1.3.3).
I am left with the following SampleJettyWebSocketsApplication code:
#Configuration
#EnableAutoConfiguration
//#ComponentScan // --- If I uncomment this the test breaks ---
#EnableWebSocket
public class SampleJettyWebSocketsApplication
implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(), "/echo").withSockJS();
}
#Bean
public EchoService echoService() {
return new DefaultEchoService("Did you say \"%s\"?");
}
#Bean
public WebSocketHandler echoWebSocketHandler() {
return new EchoWebSocketHandler(echoService());
}
public static void main(String[] args) {
SpringApplication.run(SampleJettyWebSocketsApplication.class, args);
}
}
And the following test class (code straight from the Spring Boot samples):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(SampleJettyWebSocketsApplication.class)
#WebIntegrationTest({"server.port=0"})
#DirtiesContext
public class SampleWebSocketsApplicationTests {
private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class);
#Value("${local.server.port}")
private int port = 1234;
#Test
public void echoEndpoint() throws Exception {
logger.info("Running the echoEndpoint test. Port: " + port + ". Path: /echo/websocket");
ConfigurableApplicationContext context = new SpringApplicationBuilder(
ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
.properties("websocket.uri:ws://localhost:" + this.port
+ "/echo/websocket")
.run("--spring.main.web_environment=false");
long count = context.getBean(ClientConfiguration.class).latch.getCount();
AtomicReference<String> messagePayloadReference = context
.getBean(ClientConfiguration.class).messagePayload;
context.close();
assertThat(count).isEqualTo(0);
assertThat(messagePayloadReference.get())
.isEqualTo("Did you say \"Hello world!\"?");
}
#Configuration
static class ClientConfiguration implements CommandLineRunner {
#Value("${websocket.uri}")
private String webSocketUri;
private final CountDownLatch latch = new CountDownLatch(1);
private final AtomicReference<String> messagePayload = new AtomicReference<String>();
#Override
public void run(String... args) throws Exception {
logger.info("Waiting for response: latch=" + this.latch.getCount());
if (this.latch.await(10, TimeUnit.SECONDS)) {
logger.info("Got response: " + this.messagePayload.get());
}
else {
logger.info("Response not received: latch=" + this.latch.getCount());
}
}
#Bean
public WebSocketConnectionManager wsConnectionManager() {
logger.info("Setting up SimpleClientWebSocketHandler...");
WebSocketConnectionManager manager = new WebSocketConnectionManager(client(),
handler(), this.webSocketUri);
manager.setAutoStartup(true);
return manager;
}
#Bean
public StandardWebSocketClient client() {
return new StandardWebSocketClient();
}
#Bean
public SimpleClientWebSocketHandler handler() {
logger.info("Creating new SimpleClientWebSocketHandler using SimpleGreetingService...");
return new SimpleClientWebSocketHandler(greetingService(), this.latch,
this.messagePayload);
}
#Bean
public GreetingService greetingService() {
return new SimpleGreetingService();
}
}
}
Running the Application and the unit test as above all is fine but if I uncomment the #ComponentScan annotation on the application class the application still runs OK but the test breaks with the error:
Could not resolve placeholder 'websocket.uri' in string value "${websocket.uri}".
I have read at setting-the-run-time-properties-on-springapplicationbuilder that:
The properties you configure on SpringApplicationBuilder are made available in your application's Environment, not as system properties.
And in the #ComponentScan javadoc that:
If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
But I don't understand why the behaviour changes when the #ComponentScan annotation is added.
How can I set the System Property websocket.uri in the test when the application class is annotated with #ComponentScan (or #SpringBootApplication)?
(I aim to use #SpringBootApplication, which incorporates #ComponentScan, but I can't until I get this working.)
There are several ways to add a system properties.
Solution 1:
Add arguments for Test in format of -Dabc=xyz, that will add property abc to system properties.
Solution 2:
Just like floor 0.
Solution 3:
Just let spring-boot load the properties, such as classpath:bootstrap.yml, and you can specify whatever properties in there.
The annotation #ComponentScan will enable auto scanning based on current package or ComponentScan#basePackages. Which means SampleWebSocketsApplicationTests.ClientConfiguration will be scanned cause they have same base package samples.websocket.jetty.
However, SampleWebSocketsApplicationTests.ClientConfiguration should not be parsed by SpringJUnit4ClassRunner cause we need parse it in SampleWebSocketsApplicationTests#echoEndpoint manually. It's should only be parsed by ApplicationContext created in echoEndpoint().
What's more, #SpringBootApplication equals to use #Configuration and #EnableAutoConfiguration and #ComponentScan together, so comment out #ComponentScan or #SpringBootApplication will have same effect.
My suggestion is move class SampleWebSocketsApplicationTests into package samples.websocket.jettytest(different from samples.websocket.jetty) and enable #ComponentScan or #SpringBootApplication on SampleJettyWebSocketsApplication and try again. It should work.
Adding my thoughts on this (from whatever i could gather from your code):
-Try adding the property websocket.uri in you application properties or if your project contains src/test/resources/test.properties then add it into your test.properties file.#ComponentScan should pick it up.
-Else,you could just say :
public static void main(String[] args) {
System.setProperty("websocket.uri","<your uri>");
SpringApplication.run(SampleJettyWebSocketsApplication.class, args);
}
Hope it helps.

#ComponentScan not working in test with spring-boot-starter-test

I am attempting to test my #Service and #Repository classes in my project with spring-boot-starter-test and #Autowired is not working for the classes I'm testing.
Unit test:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = HelloWorldConfiguration.class
//#SpringApplicationConfiguration(classes = HelloWorldRs.class)
//#ComponentScan(basePackages = {"com.me.sbworkshop", "com.me.sbworkshop.service"})
//#ConfigurationProperties("helloworld")
//#EnableAutoConfiguration
//#ActiveProfiles("test")
// THIS CLASS IS IN src/test/java/ AND BUILDS INTO target/test-classes
public class HelloWorldTest {
#Autowired
HelloWorldMessageService helloWorldMessageService;
public static final String EXPECTED = "je pense donc je suis-TESTING123";
#Test
public void testGetMessage() {
String result = helloWorldMessageService.getMessage();
Assert.assertEquals(EXPECTED, result);
}
}
Service:
#Service
#ConfigurationProperties("helloworld")
// THIS CLASS IS IN /src/main/java AND BUILDS INTO target/classes
public class HelloWorldMessageService {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message=message;
}
}
The commented class annotations on the unit test represent the various things I've tried to get this working. The test and the project packages are in the same package paths and the #ComponentScan works fine from my entry point (#RestController class with main method). The service #ComponentScan's and #Autowire's fine in my #RestController class in the src/main/java side, but does not in the test. I am required to add it again as a #Bean in my #Configuration class in order for #Autowired to work. The class is otherwise in scope just fine and I can reference and instantiate it just fine from the test. The problem appears to be that #ComponentScan does not appear to correctly traverse multiple entries in my test runner classpath, in this case /target/test-classes and /target/classes.
The IDE I am using is IntelliJ IDEA 13.
UPDATE - here are HelloWorldRs and its config:
#RestController
#EnableAutoConfiguration
#ComponentScan
public class HelloWorldRs {
// SPRING BOOT ENTRY POINT - main() method
public static void main(String[] args) {
SpringApplication.run(HelloWorldRs.class);
}
#Autowired
HelloWorldMessageService helloWorldMessageService;
#RequestMapping("/helloWorld")
public String helloWorld() {
return helloWorldMessageService.getMessage();
}
}
...
#Configuration
public class HelloWorldConfiguration {
#Bean
public Map<String, String> map() {
return new HashMap<>();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldMessageService helloWorldMessageService() {
return new HelloWorldMessageService();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldRs helloWorldRs() {
return new HelloWorldRs();
}
}
First, I'd recommend to use a newer #RunWith(SpringRunner.class) but that makes no difference, it is just shorter (and recommended).
Second, from the #EnableAutoConfiguration I see that you are using spring boot - which is certainly a good thing. There are some good reasons why not to use #ComponentScan directly. Can you try the following?
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes=YourApplication_or_other_Configuration.class)
public class HelloWorldTest {
... etc.
I don't know if this will turn out to be the solution, but don't use the default package (i.e. don't put *.java in "src/main/java" directly), and definitely don't use a #ComponentScan or #EnableAutoConfiguration in the default package. You will end up killing your application on startup as it tries to scan everything on the classpath (including all the Spring libraries).
SpringBoot 2.7.3, JUnit 5.8.2
If you want to have full control about the spring's configuration (and not rely on the hidden magic of auto configuration) I suggest to create an explicit configuration class:
#ComponentScan(basePackages = { "my.package.to.scan" })
public class MySpringTestConfig
{
// just for spring configuration annotations
}
and reference it in your test class:
#ContextConfiguration(classes = { MySpringTestConfig.class })
#ExtendWith({ SpringExtension.class })
class MySpringTest
{
...
}

Categories