Turn off embedded Elasticsearch in Spring Boot test - java

By default, Spring Boot will create an embedded Elasticsearch. It can be turned off by setting spring.data.elasticsearch.cluster-nodes. However, I'm not sure how to do this in a JUnit test. For example, I have:
#Slf4j
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(RemoteElasticsearch.class)
#SpringBootApplication(
scanBasePackageClasses = {
}
)
#EnableElasticsearchRepositories(basePackages = "com.example.me.repo")
public class RemoteElasticsearch {
#Inject
private SomeRepo someRepo;
#Test
public void test(){
someRepo.save(new Something());
}
}
It connects to the remote elasticsearch if I set the appropriate environment variable (eg spring.data.elasticsearch.cluster-node=host:9300). Can I somehow set this value directly on this test?

Just create second application.properties file in src/test/resources with spring.data.elasticsearch.cluster-nodes disabled. Spring Boot will use this file instead PROD configuration from src/main/resources.

Related

How to test the binding of configuration properties in Spring Boot?

I have a Spring Boot Application with the following (simplified) configuration class
#ConfigurationProperties(prefix = "prefix")
#Configuration
public class ConfigProperties {
#NotNull
public Duration snapshotOffset;
}
My code is working, but I would like know how I can write unit tests for the binding process with different property files as input?
You can configure test properties files by using the locations or value attribute of the TestPropertySource annotation :
//Typically, #TestPropertySource will be used in conjunction with #ContextConfiguration.
#ContextConfiguration
#TestPropertySource("/test.properties")
public class Test {
// class body...
}

How to load fixture dataset for integration test in spring data neo4j (SDN5)

I want to write integration tests in a project which uses spring data neo4j version 5 (SDN5) in context of a (non-web) spring boot application. For the integration tests I would like to import a defined dataset before each test to get a initial starting point (fixture) of the graph-db, but I don't know how to load it. Does someone know how to do it?
I'm using neo4j 3.4.3, SDN5, Spring Boot 2.0, JUnit 5.1.
I get integration tests in general to run and execute against an embedded instance via
#ExtendWith(SpringExtension.class)
#DataNeo4jTest(
excludeFilters = #ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE, value = ApplicationRunner.class
)
)
#ComponentScan(basePackageClasses = {TransformationService.class})
#ActiveProfiles("test")
class Neo4jAgentAutomatonTest {
#Test
void getStates() {
...
}
}
a) When you also add neo4j-ogm-test to your project it provides you the TestUtils class.
This class has the method readCQLFile that parses the file and returns the cypher query.
b) You could also read the file on your own without this dependency.
In the end you add a SessionFactory dependency as autowired in your class and execute the resulting query
class Test {
#Autowired
private SessionFactory sessionFactory;
#Before
public void setUp() {
Session session = sessionFactory.openSession();
session.query(
TestUtils.readCQLFile("<filePath>").toString(), emptyMap());
// ....
}
}

Set property with wiremock random port in spring boot test

I have a Spring Boot test that uses wiremock to mock an external service. In order to avoid conflicts with parallel builds I don't want to set a fixed port number for wiremock and would like to rely on its dynamic port configuration.
The application uses a property (external.baseUrl) set in the application.yml (under src/test/resources). However I didn't find a way to programmatically override that. I've tried something like this:
WireMockServer wireMockServer = new WireMockServer();
wireMockServer.start();
WireMock mockClient = new WireMock("localhost", wireMockServer.port());
System.setProperty("external.baseUrl", "http://localhost:" + wireMockServer.port());
but it didn't work and the value in application.yml was used instead. All other solutions that I've looked at override the property with a static value (for example in some annotation), but I don't know the value of the wiremock port until the test is run.
Clarification:
Both spring boot and wiremock run on random ports. That's fine and I know how to get the value of both ports. However wiremock is supposed to mock an external service and I need to tell my application how to reach it. I do this with the external.baseUrl property. The value I want to set in my test depends of course on the wiremock port number. My problem is simply how to programmatically set a property in a spring boot test.
The property name mentioned in https://stackoverflow.com/a/48859553/309683 (i.e. wiremock.port) is not correct, at least since Spring Cloud Contract version 2.1.2.RELEASE.
1. Working example
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWireMock(port = 0)
public class PortServiceTest {
#Autowired
private Environment environment;
#Test
public void shouldPopulateEnvironmentWithWiremockPort() {
assertThat(environment.containsProperty("wiremock.server.port")).isTrue();
assertThat(environment.getProperty("wiremock.server.port")).matches("\\d+");
}
}
2. Other WireMock properties
Other than wiremock.server.port, #AutoConfigureWireMock populates the environment with some other properties too:
wiremock.server.https-port
wiremock.server.stubs[]
wiremock.server.files[]
3. Gradle dependencies
To use Spring Cloud Contract WireMock in a Gradle based project, add the following dependency to your project:
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:${version}'
4. Using in application.yaml files
If you configure your test application.yaml file like this:
sample:
port: ${wiremock.server.port}
And define the following beans:
#Component
#ConfigurationProperties(prefix = "sample")
#Data
public class PortProperties {
private Integer port;
}
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class PortService {
private final PortProperties config;
public Integer getPort() {
return config.getPort();
}
}
You can verify that sample.port is set to the randomly chosen wiremock port:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWireMock(port = 0)
public class PortServiceTest {
#Autowired
private Environment environment;
#Autowired
private PortService portService;
#Test
public void shouldReturnWireMockPort() {
assertThat(portService.getPort())
.isNotNull()
.isEqualTo(Integer.parseInt(environment.getProperty("wiremock.server.port")));
}
}
Use property substitution in your application.properties:
external.baseUrl=http://exampleUrl:${wiremock.server.port}
This requires the wiremock.server.port property to be set before the SpringBootTest is initialised, which can be achieved by adding the #AutoConfigureWireMock annotation to your test class.
Consider using Spring Cloud Contract Wiremock
There is already a JUnit Rule builder which allows to specify ${wiremock.port} to set random port in property/yaml files
Or you can use WireMockRestServiceServer to bind WireMock to your RestTemplate so you don't even need to override URLs in your tests.
I could not find a way to override properties in a Spring Boot integration test, since the test is run only after the application is created and all the beans already configured.
As a work around I added a #TestConfiguration to the test to replace the beans in the application:
private static WireMockServer wireMockServer1 = getWireMockServer();
private static WireMockServer wireMockServer2 = getWireMockServer();
private static WireMockServer wireMockServer3 = getWireMockServer();
private static WireMockServer getWireMockServer() {
final WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
return wireMockServer;
}
#TestConfiguration
static class TestConfig {
#Bean
#Primary
public BeanUsingAProperty1 getBean1() {
BeanUsingAProperty myBean = new BeanUsingAProperty();
myBean.setPort(wireMockServer.port());
return myBean;
}
#Bean
#Primary
public BeanUsingAProperty2 getBean2() {
String baseUrl = "http://localhost:" + wireMockServer2.port();
return new BeanUsingAProperty2(baseUrl);
}
#Bean
#Primary
public BeanUsingAProperty3 getBean3() {
String baseUrl = "http://localhost:" + wireMockServer3.port() + "/request";
return new BeanUsingAProperty3(new RestTemplate(), baseUrl, "someOtherParameter");
}
}
This effectively replaced the BeanUsingAProperty with the one defined in the test so that it has the correct port number for Wiremock.
For this configuration to be picked up I had to add this class in the test annotation
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {
MySpringBootApplication.class, MyIntegrationTest.TestConfig.class })
Note that I use the non-static Wiremock API, since I have several such external services that each need to be mocked. Note that how the different beans are built is different depending on how each was designed.
The approach I use to programmatically change a property when starting a Spring Boot app, is to pass the custom value into the application main entry-point String[] args. This will have the effect of over-riding all other means such as System properties, YML or other config files.
Here is an example:
String[] args = new String[]{"--my.prop=foo"};
SpringApplication.run(Application.class, args);
It will be easy for you to expose a static method or custom API which starts the Spring Boot app (for testing) and with the value you want.
And then, once you have the value of the wiremock port - things are easy. Here is an example: PaymentServiceContractTest.java
P.S. Karate (the open-source test examples I am using above) is a new alternative to WireMock, do check it out ;)
How are you reading external.baseUrl?
If you are using a #Value annotated property, you can use ReflectionTestUtils to set the port after you have setup the mock server.
ReflectionTestUtils.setField(yourTestClass, "youPort", wireMockServer.port());
When you use org.springframework.cloud:spring-cloud-contract-wiremock dependency, if you are a fan of annotation, you can just add #AutoConfigureWireMock(port = Options.DYNAMIC_PORT)

Unit testing of Spring Boot Actuator endpoints not working when specifying a port

recently I changed my spring boot properties to define a management port.
In doing so, my unit tests started to fail :(
I wrote a unit test that tested the /metrics endpoint as follows:
#RunWith (SpringRunner.class)
#DirtiesContext
#SpringBootTest
public class MetricsTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
/**
* Called before each test.
*/
#Before
public void setUp() {
this.context.getBean(MetricsEndpoint.class).setEnabled(true);
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
/**
* Test for home page.
*
* #throws Exception On failure.
*/
#Test
public void home()
throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/metrics"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Previously this was passing. After adding:
management.port=9001
The tests started failing with:
home Failed: java.lang.AssertionError: Status expected: <200> but was: <404>
I tried changing the #SpringBootTest annotation with:
#SpringBootTest (properties = {"management.port=<server.port>"})
Where is the number used for the server.port. This didn't seem to make any difference.
So then changed the management.port value in the property file to be the same as the server.port. Same result.
The only way to get the test to work is remove the management.port from the property file.
Any suggestions/thoughts ?
Thanks
For Spring Boot 2.x the integration tests configuration could be simplified.
For example simple custom heartbeat endpoint
#Component
#Endpoint(id = "heartbeat")
public class HeartbeatEndpoint {
#ReadOperation
public String heartbeat() {
return "";
}
}
Where integration test for this endpoint
#SpringBootTest(
classes = HeartbeatEndpointTest.Config.class,
properties = {
"management.endpoint.heartbeat.enabled=true",
"management.endpoints.web.exposure.include=heartbeat"
})
#AutoConfigureMockMvc
#EnableAutoConfiguration
class HeartbeatEndpointTest {
private static final String ENDPOINT_PATH = "/actuator/heartbeat";
#Autowired
private MockMvc mockMvc;
#Test
void testHeartbeat() throws Exception {
mockMvc
.perform(get(ENDPOINT_PATH))
.andExpect(status().isOk())
.andExpect(content().string(""));
}
#Configuration
#Import(ProcessorTestConfig.class)
static class Config {
#Bean
public HeartbeatEndpoint heartbeatEndpoint() {
return new HeartbeatEndpoint();
}
}
}
For Spring boot test we need to specify the port it needs to connect to.
By default, it connects to server.port which in case of actuators is different.
This can be done by
#SpringBootTest(properties = "server.port=8090")
in application.properties we specify the management port as below
...
management.server.port=8090
...
Did you try adding the following annotation to your test class?
#TestPropertySource(properties = {"management.port=0"})
Check the following link for reference.
Isn't there an error in the property name?
Shouldn't be
#TestPropertySource(properties = {"management.server.port=..."}) instead of #TestPropertySource(properties = {"management.port=.."})
The guide stated that this can be achieved with #AutoConfigureMetrics.
And I moved with this.
Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using #SpringBootTest.
If you need to export metrics to a different backend as part of an integration test, annotate it with #AutoConfigureMetrics.
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.metrics
Had the same issue, you just have to make the management.port null by adding this in your application-test.properties (set it to empty value)
management.port=
Make sure you use the test profile in your JUnit by annotating the class with
#ActiveProfiles("test")
Try using
#SpringBootTest(properties = {"management.port="})
Properties defined in the #SpringBootTest annotation have a higher precedence than those in application properties. "management.port=" will "unset" the management.port property.
This way you don't have to worry about configuring the port in your tests.
I was facing the same issue and tried several things but this is how I was able to solve mine without making any change in the application.yaml
Sample actuator endpoint
#Component
#RestControllerEndpoint(id = "endpoint")
public class SampleEndpoint
{
#GetMapping
public String sampleEndpoint(){
return ""
}
}
Unit test case
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {SampleEndpointTest.Config.class},
properties = {"management.server.port="}
)
#AutoConfigureMockMvc
public class SampleEndpointTest
{
#Autowired
private MockMvc mockMvc;
#SpringBootApplication(scanBasePackageClasses = {SampleEndpoint.class})
public static class Config
{
}
#Test
public void testSampleEndpoint() throws Exception
{
mockMvc.perform(
MockMvcRequestBuilders.get("/actuator/enpoint").accept(APPLICATION_JSON)
).andExpect(status().isOk());
}
Since now info endpoint must be enabled manually make sure the SpringBootTest tag includes this in properties, like this:
#SpringBootTest(
properties = {
"management.info.env.enabled=true" ,
"management.endpoints.web.exposure.include=info, health"
})
I had this problem recently, and as none of the above answers made any sense to me, I decided to do a bit more reading. In my case, I had already defined both server.port and management.server.port as 8091 in my test application-test.yaml file, and could not understand why my test was getting a connection refused error message.
It turns out that instead of using the annotation #SpringBootTest() I needed to use #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) - which causes the port numbers in the yaml file to be used. This is briefly discussed in the manual. Quoting the relevant section:
DEFINED_PORT — Loads an EmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from your application.properties or on the default port 8080).
It seems in SpringBootTest the default is to avoid starting a real servlet environment, and if no WebEnvironment is explicitly specified then SpringBootTest.WebEnvironment.MOCK is used as a default.
After a long search: There is this nice Springboot annotation called #LocalManagementPort!
It works similar to #LocalServerPort but for actuator endpoins.
An example config would look as follows
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MetricsIT {
#Autowired
RestTemplateBuilder restTemplateBuilder;
#LocalManagementPort
int managementPort;
#Test
public void testMetrics(){
ResponseEntity<String> response = restTemplateBuilder
.rootUri("http://localhost:" + managementPort + "/actuator")
.build().exchange("/metrics", HttpMethod.GET, new HttpEntity<>(null), String.class);
}
}

Spring Boot Externalizing properties not working

I have looked at the below threads and followed things given there. Still my property override is not happening
Spring Boot - Externalized properties
Profile Specific Property Enablement
Spring Boot External Config
I am on Tomcat 8.0.33 and Spring boot starter web and got this in my setenv.sh
export JAVA_OPTS="$JAVA_OPTS -Dlog.level=INFO -Dspring.config.location=file:/opt/jboss/apache-tomcat-8.0.33/overrides/ -Dspring.profiles.active=dev"
And in the overrides folder I got 2 files
1) application.properties
2) application-dev.properties
The application.properties has a single entry in it
spring.profiles.active=dev
I see that the proper log.level is fed to my code which means this command is working. Its just that I am clueless as to why my override is not happening as expected
I don't have any `PropertyPlaceholderConfigurer code in my workspace. I am not even sure if I need 1
I don't use this method to externalise properties. First, I'll try a suggestion for your method and then I'll show you what I'm using.
The suggestion for your method is to use file:/// instead of file:/ as with Spring I found that when not passing the three slashes after the colon it didn't recognise the property.
I've created a sample project for you, available here with instructions.
Now for the method I use.
I define a Configuration file for each profile and I keep the application.properties file under src/main/resources.
Then I use the #Profile and #PropertySource annotations on each configuration file.
For example:
#Configuration
#Profile("dev")
#PropertySource("file:///${user.home}/.devopsbuddy/application-dev.properties")
public class DevelopmentConfig {
#Bean
public EmailService emailService() {
return new MockEmailService();
}
#Bean
public ServletRegistrationBean h2ConsoleServletRegistration() {
ServletRegistrationBean bean = new ServletRegistrationBean(new WebServlet());
bean.addUrlMappings("/console/*");
return bean;
}
}
And
#Configuration
#Profile("prod")
#PropertySource("file:///${user.home}/.devopsbuddy/application-prod.properties")
public class ProductionConfig {
#Bean
public EmailService emailService() {
return new SmtpEmailService();
}
}
I have also got a Configuration file that is valid for all profiles, which I call ApplicationConfig, as follows:
#Configuration
#EnableJpaRepositories(basePackages = "com.devopsbuddy.backend.persistence.repositories")
#EntityScan(basePackages = "com.devopsbuddy.backend.persistence.domain.backend")
#EnableTransactionManagement
#PropertySource("file:///${user.home}/.devopsbuddy/application-common.properties")
public class ApplicationConfig {
}
My src/main/resources/application.properties file looks like the following:
spring.profiles.active=dev
default.to.address=me#example.com
token.expiration.length.minutes=120
Of course I could externalise the spring.profile.active property by passing it as a system property but for my case and for now it's fine.
When running the application, if I pass the "dev" profile, Spring will load all properties and Beans defined in the DevelopmentConfig class plus all those in ApplicationConfig. If I pass "prod", the ProductionConfig and ApplicationConfig properties will be loaded instead.
I'm completing a course on how to create a Spring Boot website with Security, Email, Data JPA, Amazon Web Services, Stripe and much more. If you want, you can register your interest here and you will get notified when the course is open for enrolment.

Categories