Use custom container as database with TestContainer - java

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

Related

#BeforeAll JUnit/spring-boot-test alternative that runs when application context starts

I'm writing a #Repository/#Service integration test that leverages an embedded database. In my test class, I would like to preload my database with some data.
I'm currently using #BeforeEach to load in my sample data, however, this code is run upon each test in my class.
Is there any way that I can load in my test data after Spring application context has loaded, but before any test has been run?
My current approach:
#BeforeEach
public void before() {
repository.save(...); // -> prepopulates repository with sample data
}
#Test
public void testService() {
service.get(...); // -> gathers existing record
}
#Test
public void deleteById() {
service.delete(...); // -> deletes existing record
}
However... with this, I am required to flush out the records after every test. Otherwise any unique constraints can easily be violated.
Rather than using #BeforeEach which is required to run before every test... is it possible to load this in in a #BeforeAll kind of fashion that happens after the spring application context has been loaded?
Is there any way that I can load in my test data after Spring application context has loaded
Basically yes, I think you can do that:
The idea is to load the SQL data when the application context is started or in the process of being started.
For example, spring boot integration with Flyway works this way (the bean of Flyway is created and loaded). So, in theory, you could merely use Flyway with test migrations that will contain all the relevant SQL scripts of test data generation.
How can you do this technically?
Here is one way:
Create a special bean (just like the way it works with Flyway) that would depend on your repository and in post construct save the data:
#Component
public class SqlGenerationBean {
#Autowired
private MyRepository repo;
#PostConstruct
public void init() {
repo.save();
}
}
Another way of doing is to create a listener that will be called upon the application context started and again will call the same repo.save().
In both cases the bean/listener code should not be accessible from production (it's only for tests): so put it somewhere under src/test/java for example
Now once the application context is started you can use a neat trick:
Mark your tests with #Transactional annotation. Spring will wrap the code in an artificial transaction that will be rolled back automatically (even if the test succeeds) so that all the data that you'll modify during the test will be rolled back and basically before each test, you'll have the same state (that is identical to the state of the database when/after the application context starts). Of course, if you use DDL in the test, some databases can't make it a part of transaction but it depends on the database really.
Another interesting point here is that the application context can be cached even between the test cases (created only once), so keep this in mind.
In this case I would just create a constructor for the test class. It will be triggered before everything.
#BeforeEach runs before each tests but after all initialisations .
you can also just use Mockito and mock the result without need to clean and overcomplicate
Just add following snippet to your code. This is just like you can do to detect that Spring application is really started.
#Configuration
public class AppConfig implements ApplicationListener<ApplicationReadyEvent> {
/**
* This is to indicate in the logs when the application has actually started and everything is loaded.
*/
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ApplicationContext context = event.getApplicationContext();
Environment env = context.getEnvironment();
// do what you want on application start
}
}
P.S. For database manipulation in test #Sql is the best candidate as was mentioned in comment.

How to overwrite ports defined in application.properties in integration tests after testcontainer started?

Assume I want to integration test code relying on a JPA datasource in a Spring Boot 2.x application with a PostgreSQL testcontainer (great tool for managing Docker containers from within test classes with one or few more lines of code). Assume further that I'm managing the ports (included in the JDBC URL) in application.properties, e.g.
spring.datasource.url=jdbc:postgresql://user-service-postgres:5432/user_service
In the integration test I create testcontainers with
#Rule
PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
In a preparation method I can access the value I want to set for spring.datasource.url with
postgreSQLContainer.getJdbcUrl()
How to tell Spring Boot in the test to use that URL instead of the one specified in application.properties.
I'd like to stick to my property files in order to minimize changes, but I'm thankful for other approaches including an explanation why they're superior or necessary as well.
I'm using Spring Boot 2.x.
Since Spring Framework 5.2.5 (Spring Boot 2.2.6) this setup is now even simpler as we can use the #DynamicPropertySource annotation and don't have to write and register a custom initializer.
Assuming you use the JUnit 5 dependency of Testcontainers, your test can look like the following:
#SpringBootTest
#Testcontainers
class ExampleIntegrationTests {
#Container
static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
#DynamicPropertySource
static void dataSourceProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
}
}
You can read more about this new feature here. I've also covered the different application properties setup ways (depending on Spring Boot and JUnit version) in a dedicated Testcontainers guide.
You can manually override the property from within your Spring-boot test by using ContextConfiguration and ApplicationContextInitializer.
Override the property - define a static inner class:
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues
.of("spring.datasource.url=" + postgreSQLContainer.getJdbcUrl())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
ApplicationContextInitializer can be used for programmatically initializing a Spring context before context refresh. Now, wire up the context initializer class by annotating at test class level with ContextConfiguration:
#ContextConfiguration(initializers = Initializer.class)
Docs:
ApplicationContextInitializer
ContextConfiguration

Dynamic Database property changes in Springboot

I have mysql database and i have configured database properties in application.properties file .
Now if i do change db connection properties , i want reflect that changes into my application on the fly , means with out restarting the server
Is this possible using with spring cloud config server and actuator?
I have tested this quite a bit and here are my findings.
Spring config server works pretty well for simple key value pairs.
It also works for Database properties provided that you are creating datasource objects yourself and you are using #RefreshScope.
For example, if you have a config server with these properties.
mongodb.feed.database=kiran
mongodb.feed.host=localhost
mongodb.feed.port=27017
And you are configuring MongoTemplate in your application like this.
#Configuration
#ConfigurationProperties(prefix = "mongodb.feed")
#EnableMongoRepositories(basePackages = "in.phani.springboot.repository", mongoTemplateRef = "feedMongoTemplate")
#Setter
class FeedMongoConfig {
private String host;
private int port;
private String database;
#Primary
#Bean(name = "feedMongoTemplate")
#RefreshScope // this is the key
public MongoTemplate feedMongoTemplate() throws Exception {
final Mongo mongoClient = createMongoClient(new ServerAddress(host, port));
return new MongoTemplate(mongoClient, database);
}
Mongo createMongoClient(ServerAddress serverAddress) {
return new MongoClient(serverAddress);
}
}
And if you change the database name in your config properties and then refresh the scope with /refresh endpoint. It works pretty well.
With springboot you need not do manual configuration like this. Spring boot has Autoconfiguration for most of the stuff. Continuing with the same example above, if you were to put in config properties something like this
spring.data.mongodb.uri=mongodb://localhost:27017/phani
spring-boot will configure MongoTemplate for you(you don't need to create yourself as in 2nd point).
Here comes the hiccup.
Now if you change the database name, and refresh the scope, it doesn't work. Because in this case, MongoTemplate was configured by spring-boot Autoconfiguration(MongoAutoConfiguration)
So in conclusion, it needs extensive testing to be done, before using it on production(especially for complex beans like datasources, MongoTemplates), since there is not enough documentation on this.. But I would say, it is worth trying.

Spring Boot Test does not use jmx property

I have a test clas with
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class Foo{
...
}
which should start up a regular application context as defined by:
#SpringBootApplication(scanBasePackages = {"de.foo", "de.bar"})
public class Application {
...
}
This works as expected. Further I have an application.yml which gets loaded in both cases but when running the test, the property for JMX (spring.jmx.enabled) does not get loaded or it does not get used.
I tried different property files (application.yml, application-test.yml) but the only thing what works is setting the property via
#TestPropertySource(properties = "spring.jmx.enabled:true")
The property defaults to true in a regular application context.
Several questions:
Why is the default different in a test class?
Why does the property not get loaded or recognized, when loading it from an application.yml (the rest of the yml works, so it does get loaded).
This seems to be a known behavior, as seen in this comment in Spring Boot Sample Data Tests. Is there any documentation I missed about this behavior?
I've recently encountered the same situation myself, and have opened spring-projects/spring-boot#13008 to document this behavior. As a result, the following additions to the reference manual will be added in the upcoming 1.5.13.RELEASE and 2.0.2.RELEASE:
As the test context framework caches context, JMX is disabled by default to prevent identical components to register on the same domain. If such test needs access to an MBeanServer, consider marking it dirty as well:
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "spring.jmx.enabled=true")
#DirtiesContext
public class SampleJmxTests {
#Autowired
private MBeanServer mBeanServer;
#Test
public void exampleTest() {
// ...
}
}

Hoverfly simulationMode conflicts with Spring Cloud Config Server in unit test

I'm using hoverfly in my spring boot project's unit test.
The background
The spring boot project will grab its config (connection timeout etc.) from spring cloud config server.
To test whether my timeout configs work, I write a unit test, and expect the hoverfly can return with a long delay , then my customized restTemplate can throw timeout error instead of wait.
The unit test looks lilke this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = TestApplication.class)
#FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
public class CustomRestTemplateTest {
#Autowired
private RestTemplate customRestTemplate;
#ClassRule
public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(SimulationSource.dsl(
service("www.test.com")
.get("/")
.willReturn(success(HttpBodyConverter.json("{}")).withDelay(10, TimeUnit.SECONDS))
));
#Test
public void connectionTimeoutTest() {
customRestTemplate.getForObject("www.test.com", Object.class);
}
}
The issue
As I mentioned in section The background, when my spring boot project starts, it will grab configs from spring cloud config server, but Hoverfly captured that request and try to find the corresponding record, of course it can't , because I only defined the records for my unit test(e.g. www.test.com), so it throws error:
{"destination":"172.16.2.84:8888","error":"No match found","key":"a7ac72c9bcc3dc2b76bf0877d98f9e3a","level":"warning","method":"GET","msg":"Failed to find matching request template from template store","path":"************","query":"","time":"2017-03-08T20:55:28+08:00"}
How could I fix this? I want use hoverfly, can I set some config and exclude config server's url?
Hoverfly's developer Tommy responded me in their email list
It's a known issue: https://github.com/SpectoLabs/hoverfly-java/issues/19
Update
This has been fixed by Tommy Situ, and the code fix will be release in v0.4.3

Categories

Resources