How to configure the database image for Springboot integration testing - java

There is a situation that I want to use a database image to integration test my service layer. Here is the code I developed to setup my Postgres container image:
#SpringBootTest(classes = EventhandlerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public abstract class BaseIT {
public static final PostgreSQLContainer<?> postgresDB = new PostgreSQLContainer<>
("postgres:12-alpine")
.withDatabaseName("test-db")
.withUsername("postgres")
.withPassword("password");
static {
postgresDB.start();
}
#DynamicPropertySource
public static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgresDB::getJdbcUrl);
registry.add("spring.datasource.username", postgresDB::getUsername);
registry.add("spring.datasource.password", postgresDB::getPassword);
}
}
Now I want to use the test database to call my service layer methods and inspect the results, here is my integration test example:
public class SampleServiceTesting extends BaseIT {
#Autowired
private SampleService sampleService;
#Test
#Transactional
void testIntegrationFlow() {
SampleService.exampleMethod();
}
}
But when I run the test, it returns the following error:
org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
How I can fix this issue on runing my test please?

You asked
Experiencing HibernateException in testcontainers with using PostgreSQLContainer
The problem lies on not having active connection into the database through a connection pool which can also cause from miss-configuration and incorrect injection of PostgreSQLContainer, cause Hibernate can determine the correct dialect to use automatically, and for doing this it needs a live connection to the database.
One practice could be using of ApplicationContextInitializer interface instead of #DynamicPropertySource for programmatic initialization of the application context. For example, registering datasources or activating profiles against the context's environment.
One practice will look like below.
static class PropertyInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + postgresDB.getJdbcUrl(),
"spring.datasource.username=" + postgresDB.getUsername(),
"spring.datasource.password=" + postgresDB.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}

Related

ApplicationContext for Test environment using TestContainer in Micronaut application not working

I am trying to test the Micronaut application using MongoDb and RabbitMQ test containers. The application.yml has the below configuration
mongodb:
uri: "mongodb://${MONGO_HOST:localhost}:${MONGO_PORT:27017}"
database: "FeteBird-Product"
I have the below code for the configuration
#Introspected
#ConfigurationProperties("mongodb")
public record MongodbConfiguration(#NotNull String uri, #NotNull String database) {
}
In the repository. The repository is in another project
#Singleton
public record Repository(MongoClient mongoClient, MongodbConfiguration mongodbConfiguration) implements IRepository {
#Override
public <T> MongoCollection<T> getCollection(String collectionName, Class<T> typeParameterClass) {
return mongoClient
.getDatabase(mongodbConfiguration.database())
.getCollection(collectionName, typeParameterClass);
}
}
mongodbConfiguration.uri is always mongodb://localhost:27017, however in the JUnit testing I have the below code
#Testcontainers
public abstract class TestContainerFixture {
public static final GenericContainer mongoDBContainer;
public static final GenericContainer rabbitMQContainer;
static {
mongoDBContainer = new GenericContainer(DockerImageName.parse("mongo:4.0.10")).withExposedPorts(27017);
rabbitMQContainer = new GenericContainer(DockerImageName.parse("rabbitmq:3-management-alpine")).withExposedPorts(5672);
mongoDBContainer.start();
rabbitMQContainer.start();
}
}
#MicronautTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class DiscountDeleteListenerTest extends TestContainerFixture {
private ApplicationContext applicationContext;
#BeforeAll
#DisplayName("Initial application setup")
void initialApplicationSetup() {
applicationContext = ApplicationContext.run(
Map.of("mongodb.uri",
String.format("mongodb://%s:%s", mongoDBContainer.getContainerIpAddress(), mongoDBContainer.getMappedPort(27017)),
"rabbitmq.uri",
String.format("amqp://%s:%s", rabbitMQContainer.getContainerIpAddress(), rabbitMQContainer.getMappedPort(5672)))
, "test"
);
iDiscountProducer = applicationContext.getBean(IDiscountProducer.class);
}
}
The below code is fine
String.format("mongodb://%s:%s", mongoDBContainer.getContainerIpAddress(), mongoDBContainer.getMappedPort(27017))
This gives the URL as mongodb://localhost:57032
Now when I finish all the unit tests and check the database it is inserting, updating, and deleting from the local docker instance that is port 27017
The host for the cluster is still pointing to 27017
Got some idea from here https://github.com/micronaut-projects/micronaut-test/issues/32 but still quite not sure how to do it.
we cant use MicronautTest and TestContainer at the same time. When you annotate the test with MicronautTest it wake up the application before to call your initialization method
So removing the #MicronautTest solve the issue

Dynamically add property sources to SpringBootTest

Similar to Springboot unit test set #Configuration Properties dynamically but the context is different.
In my case I have a TestContainer running a custom MySQL database that is prepopulated with a lot of data (not using the SQL batch loading approach because the data is an anonymized copy of production and doing it through SQLs makes the boot up time of the container 20 minutes vs 2 minutes).
So far my test looks like this
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {
Bootstrap.class
}
)
public class ITFakeDB {
#ClassRule
public static final GenericContainer DB = new GenericContainer("devdb")
.withExposedPorts(3306);
#Autowired
private DataSource dataSource;
#Autowired
private Users users;
#Test
public void testDatabaseIsUp() {
assertTrue(DB.getMappedPort(3306) != 0);
}
#Test
public void testUser() {
Optional<User> user = users.findByLoginName("mimi");
assertTrue(users.isPresent());
}
}
What I want to do is somehow set the spring.datasource.url (or in my case datasources.schema1.url because I did the routing datasource) to the one used by DB
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) {
String url = "jdbc:mysql://" + DB.getContainerIpAddress() + ":" + DB.getMappedPort(3306) + "/my_db";
TestPropertyValues
.of("datasources.schema1.url=" + url)
.applyTo(configurableApplicationContext.getEnvironment());
}
}
Note: I have assumed that the url is derived from the ip address, port and db name. You may change that part as needed but the core idea remains.
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
While the previous answer should work, Spring Framework 5.2.5 (that is included into Spring Boot 2.2.6) has introduced a new #DynamicPropertySource annotation exactly for that case:
#DynamicPropertySource
static void initializeDatasource(DynamicPropertyRegistry registry) {
String ip = DB.getContainerIpAddress();
Integer port = DB.getMappedPort(3306);
String url = String.format("jdbc:mysql://%s:%d/my_db", ip, port);
registry.add("datasources.schema1.url", url);
}
See for details:
Blog: #DynamicPropertySource in Spring Framework 5.2.5 and Spring Boot 2.2.6
Documentation: Context Configuration with Dynamic Property Sources

Using #PostConstruct in a test class causes it to be called more than once

I am writing integration tests to test my endpoints and need to setup a User in the database right after construct so the Spring Security Test annotation #WithUserDetails has a user to collect from the database.
My class setup is like this:
#RunWith(value = SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
public abstract class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Service aService;
#PostConstruct
private void postConstruct() throws UserCreationException {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void testA() {
// Some test
}
#Test
public void testB() {
// Some test
}
#Test
public void testC() {
// Some test
}
}
However the #PostConstruct method is called for every annotated #Test, even though we are not instantiating the main class again.
Because we use Spring Security Test (#WithUserDetails) we need the user persisted to the database BEFORE we can use the JUnit annotation #Before. We cannot use #BeforeClass either because we rely on the #Autowired service: aService.
A solution I found would be to use a variable to determine if we have already setup the data (see below) but this feels dirty and that there would be a better way.
#PostConstruct
private void postConstruct() throws UserCreationException {
if (!setupData) {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
setupData = true;
}
}
TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener.
In JUnit, the test class constructor is invoked at each test method executed.
So it makes sense that #PostConstruct be invoked for each test method.
According to JUnit and Spring functioning, your workaround is not bad. Specifically because you do it in the base test class.
As less dirty way, you could annotate your test class with #TestExecutionListeners and provide a custom TestExecutionListener but it seem overkill here as you use it once.
In a context where you don't have/want the base class and you want to add your boolean flag in multiple classes, using a custom TestExecutionListener can make sense.
Here is an example.
Custom TestExecutionListener :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class MyMockUserTestExecutionListener extends AbstractTestExecutionListener{
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
MyService myService = testContext.getApplicationContext().getBean(MyService.class);
// ... do my init
}
}
Test class updated :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS,
value=MyMockUserTestExecutionListener.class)
public abstract class IntegrationTests {
...
}
Note that MergeMode.MERGE_WITH_DEFAULTS matters if you want to merge TestExecutionListeners coming from the Spring Boot test class with TestExecutionListeners defined in the #TestExecutionListeners of the current class.
The default value is MergeMode.REPLACE_DEFAULTS.

Spring Boot tests. Initilize database programatically

I develope a WebApp using Spring Boot. I need to make integration tests with database. I have a problem in database initilization. I know it is possible to prepare database with initilazion scripts. And I do it partially. But some records have type of BLOB and it is annoying to initilize it by the script. So I'm trying to init this records programatically from #Test method using CrudRepository implementations from ApplicationContext (that are encapsulated in PersistenceService).
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class})
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DatabaseSetup(MyTest.DATASET)
#DatabaseTearDown(type = DatabaseOperation.DELETE_ALL, value = { MyTest.DATASET })
#DirtiesContext
public class MyTest {
protected static final String DATASET = "classpath:dbunit/customer.xml";
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private PersistenceService persistenceService;
#Test
#Transactional
#Rollback(false)
public void afterGroupDeleteCrlShouldContainsAllCertificates() throws Exception {
prepareDatabase(persistenceService);
restTemplate.delete("/customer/");
}
But marking of #Test method by #Transactional is the reason of deadlock when I'm calling restTemplate.delete() because there is uncommited transaction.
So I'm trying to commit transaction manually adding after calling of prepareDatabase(persistenceService) this snippet:
if (TestTransaction.isActive()) {
TestTransaction.flagForCommit();
TestTransaction.end();
}
This snippet fixes deadlock but generate SQLException after test execution
java.sql.SQLException: PooledConnection has already been closed.
I'm sure it is common task. But I don't know how to resolve it gracefully.
I think I'm missing some parts, but how about putting the prepareDatabase into a #Before method in your test.
#Before
public void setupDatabaseForEachTest(){
prepareDatabase(persistenceService);
}
I think that will allow your #Test to be #Transactional and give you the desired results.

Elasticsearch Spring boot integration test

I am looking for the way to add embedded elasticsearch to my spring boot integration test.
I looked at elastic search integration test but it does not work together with spring boot as both should uses different test runner.
I have a class test as below unfortunately it does not work with error:
java.lang.IllegalStateException: No context information for thread:
Thread[id=1, name=main, state=RUNNABLE, group=main]. Is this thread
running under a class
com.carrotsearch.randomizedtesting.RandomizedRunner runner context?
Add #RunWith(class
com.carrotsearch.randomizedtesting.RandomizedRunner.class) to your
test class. Make sure your code accesses random contexts within
#BeforeClass and #AfterClass boundary (for example, static test class
initializers are not permitted to access random contexts).
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = App.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
public class TestExample extends ElasticsearchIntegrationTest {
TestRestTemplate testRestTemplate = new TestRestTemplate();
#Value("${local.server.port}")
int port;
#Test
public void testOne(){
ResponseEntity<String> results = testRestTemplate.getForEntity(String.format("http://localhost:%d/client/1", port), String.class);
System.out.print(results);
}
}
Does anybody has some ideas how to make them run or what is alternatives ??
You can actually do what you need without any additional elasticsearch testing dependencies. The idea is basically to create an embedded node and then use the NodeClient to communicate with it.
For that, I created my own EmbeddedElasticsearchServer class which looks (more or less) like this:
public class EmbeddedElasticsearchServer implements InitializingBean {
public EmbeddedElasticsearchServer() {
ImmutableSettings.Builder elasticsearchSettings = ImmutableSettings.settingsBuilder()
.put("http.enabled", "false")
.put("path.data", "target/elasticsearch-data");
node = nodeBuilder()
.local(true)
.settings(elasticsearchSettings.build())
.node();
client = node.client();
}
#Override
public void afterPropertiesSet() throws Exception {
// Initialization stuff:
// - create required indices
// - define mappings
// - populate with test data
}
public Client getClient() {
return client;
}
}
Then, in spring configuration (let's call it integration-test-context.xml) I did this:
<bean id="embeddedElasticsearchServer"
class="com.example.EmbeddedElasticsearchServer" />
<bean id="elasticsearchClient"
class="org.elasticsearch.client.node.NodeClient"
factory-bean="embeddedElasticsearchServer"
factory-method="getClient" />
Then you can just autowire the client in your test like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/integration-test-context.xml")
public abstract class AbstractElasticsearchIntegrationTest {
#Autowired
private Client elasticsearchClient;
// Your rests go here...
}

Categories