How to get the running server port in a SpringBoot test? - java

I'm creating some unit tests for a spring boot application with an Apache Camel route, using Spock as testing framework, and I need to mock a response from another application. I made a mock controller for that, but i need to inject the port that the test is running in to a property. Is there a way to get the port that the test is running on?
I tried with
#LocalServerPort
private int port
and with
#Autowired Environment environment;
String port = environment.getProperty("local.server.port");
but both return a -1, I donĀ“t know any other ways to get the port
My test is configured with the following annotations:
#RunWith(SpringRunner)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles('test')
Also, is there a way to inject that random port in the application-test.yml file? Ideally I would need to do something like this in my application-test.yml file:
app:
service: localhost:${server.port}
Where the port is the random port that the test is running on.

Could you try this :
#SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
public class test{
#LocalServerPort
private int rdmServerPort;
#LocalManagementPort
private int rdmManagementPort;
...
}

Related

Data Jpa Test fails to load properties

I am using Hashi Corp vault in Spring Boot project. I am able to run the project without any issue. But when I run unit tests, secret-id and role-id are not being passed. I tried the following but got an exception saying both are empty. Tried hard coding the values, that didn't work either
EmployeeTest.java
#RunWith(SpringRunner.class)
#DataJpaTest
#ActiveProfiles(value = "ide")
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class EmployeeTest
{
private final Logger logger= LoggerFactory.getLogger(getClass());
#Autowired
EmployeeRepository employeeRepository;
#Test
public void getEmployeeById()
{
Employee employee=employeeRepository.getOne(13L);
logger.info(employee.toString());
}
}
Update:
I am able to pass secret-id and role-id through VM arguments but still properties are not resolving
Okay, it turns out when using profile from src/main/resources/application-ide.yml in spring boot test, properties are not being replaced by vault vaules. Copying the same file to src/test/resources/application-ide.yml fixes the issue.
TL;DR
For Spring Boot testing always better to use properties file from src/test/resources rather than src/main/resources

Use custom container as database with TestContainer

I'm trying to use TestContainer for an integration test of a Spring Boot application.
The database of such application resides in a custom PostgreSQL Docker image.
In the integration test the ApplicationContext is started through MockMvc and the container is started with something like
public class ITMyServiceTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Rule
public PostgreSQLContainer testPostgres = new PostgreSQLContainer("my-custom-database-image")
.withDatabaseName("my_db")
.withUsername("my_name")
.withPassword("my_pwd");
#Test
public void shouldDoSomething() throws Exception {
this.mockMvc.perform(get("/api/do/something")).andDo(print());
}
What happens now is that the container is started, but the application context doesn't refer to it.
I can't use the JDBC URL scheme (like spring.datasource.url=jdbc:tc:postgresql://localhost/my_db) in a .properties file because:
if I specify postgresql as server it will start another server and the context will use it
if I specify the name of my container (or everything else) the test will rightly raise a java.lang.IllegalArgumentException because it is not a known database type.
How can I set the Spring' application context to refer to my container?
You can probably have different application.properties like application-it.properties and there define integration test specific configuration properties as - #TestPropertySource(locations = {"classpath:application-it.properties" } or have different profile(s) active during integration tests , like -#ActiveProfiles(AppConstants.INTEGRATIONTEST)
So I guess you would like to set your application's database port to the randomly generated database port. If that's the case then can first expose the port :
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("my-custom-database-image")
.withDatabaseName("my_db")
.withUsername("my_name")
.withPassword("my_pwd")
.withExposedPorts(5432);
Following that when you are setting the spring.datasource.url, you can set there the randomly generated port(which is mapped against the exposed port) from the postgresql container, how to set dynamic values inside application properties is shown in this blog post

Inject / override a properties value to a Spring Boot properties file in unit/integration tests phase

Working with Spring Boot and Testcontainers I need a way to dynamically tell the app what is the port in which the testcontainer is listening.
I know that during tests I can tell Spring to use a different properties file:
#TestPropertySource(locations = "classpath:application-integrationtests.yml")
But since the port will be random, I need to programmatically inject the value to the Spring or to the properties file.
I'm not talking about #Value parameter as it will inject to the bean a value from the properties file, because when the app is in test phase, there is no way to know what this value will be.
Following #Dirk Deyne great link to an example from testcontainers demo I'm adding here a copy (with small modifications) of Testcontainer's solution to the above question:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = DemoApplication.class,webEnvironment =
WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = MyIntegrationTest.Initializer.class)
public class MyIntegrationTest {
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues values = TestPropertyValues.of(
"some.value.1=" + someObject.getSomeValue(),
"some.value.2=" + someObject.getOtherValue()
);
values.applyTo(configurableApplicationContext);
}
}
}
There's probably a better way, but I've just been using System properties for this.
#SpringBootTest
#DirtiesContext
public class MyTest {
#BeforeClass
public static void setUpEnvironment() {
System.setProperty("kafka.bootstrap.servers", testKafka.getServers();
}
...
}
Hard to write a correct answer as you don't show the code where you use the Testcontainers. But from the documentation:
The class rule provides methods for discovering how your tests can interact with the containers:
getContainerIpAddress() returns the IP address where the container is listening
getMappedPort(...) returns the Docker mapped port for a port that has been exposed on the container
For example, with the Redis example above, the following will allow your tests to access the Redis service:
String redisUrl = redis.getContainerIpAddress() + ":" + redis.getMappedPort(6379);
So you should be able to easily access this information.

How to run a integration test in Spring with #SpringBootTest

I am trying to learn integration tests with Spring. So I am following this tutorial:
http://www.lucassaldanha.com/unit-and-integration-tests-in-spring-boot/
I am fase a test Class like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class GreetingControllerTest {
#Test
public void helloTest(){
TestRestTemplate restTemplate = new TestRestTemplate();
Hello hello = restTemplate.getForObject("http://localhost:8080/hello", Hello.class);
Assert.assertEquals(hello.getMessage(), "ola!");
}
}
But when I mvn install, I get this error:
I/O error on GET request for "http://localhost:8080/hello": Connection refused; nested exception is java.net.ConnectException: Connection refused
So... What am I doing wrong? What I need to do to make my test work?
Note: If I run mvn spring-boot:run the project works fine and I request the end point using any browser.
That's because of the following property in your test class:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
As per spring documentation, it binds the application to a random port. So, while sending the request, there's a chance that the app won't be running on port 8080 and hence, you get connection refused error.
If you want to run the app on a particular port, you need to remove webEnvironment property and annotate your class with the following:
#IntegrationTest("server.port=8080")
Another approach is to get the port and add it into the url, below is the snippet to get the port:
#Autowired
Environment environment;
String port = environment.getProperty("local.server.port");
You can autowire the random port value to a field in the test class if you want to:
#LocalServerPort
int port;
but you can autowire the restTemplate and you should be able to use it with relative URI without the need to know the port number:
#Autowired
private TestRestTemplate restTemplate;
#Test
public void helloTest(){
Hello hello = restTemplate.getForObject("/hello", Hello.class);
Assert.assertEquals(hello.getMessage(), "ola!");
}

How do I configure a per-test-method instantiation of a SpringBoot with Junit?

I have a unit test that instantiates the application once for JUnit test class:
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "server.port=9000",
classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class TodoWebDriverTest {
// many test methods
}
But I wish to tear down and instantiate the application (socket listener 'n' all) per test method. I am relying on a couple of annotations presently (see above) but would happily abandon them for a reliable pure-Java launch/start capability:
app = SpringSomething.prepare(Appication.class, other params);
app.start();
waitForProofOfBeingStarted(app);
Here, I forked someone else's application - https://github.com/paul-hammant/todo-backend-spring4-java8 - and added wire-mocking (without a service virtualization framework) and three WebDriver tests. All in all the build is about 30 seconds. This shows a once-per-test-class app setup, and is towards a larger solution that will show multiple Junit testing phases in a pipeline, but I need to flip it to once-per-test-method app setup.
You want the #DirtiesContext annotation
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "server.port=9000",
classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
public class TodoWebDriverTest {
// many test methods
}

Categories

Resources