How to populate data in testcontainers? - java

CONTEXT:
I have a non Spring Boot app which I wrap in Spring Boot in order to test functionality. It happened that code had an in-memory database in tests, I want to test against a different database to reproduce the production environment. I decided to go with testcontainers to keep all existing tests untouched.
PROBLEM:
I need to load millions of rows into the test container. The question is how to populate the database when in testcontainers? I found a similar question -> How to populate testcontainers? but I still cannot get how to populate data in it.
How can I populate data in testcontainers?
DatabaseTestInitalizer.java I use for instantiating container:
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.testcontainers.containers.MSSQLServerContainer;
public class DatabaseTestInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private MSSQLServerContainer mssqlServerContainer;
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
mssqlServerContainer = new MSSQLServerContainer();
mssqlServerContainer.start();
// This is solution for 1.x.x Spring Boot framework
// Article for migration from 1.x.x to 2.x.x Spring Boot https://stackoverflow.com/questions/54718995/appropriate-usage-of-testpropertyvalues-in-spring-boot-tests
EnvironmentTestUtils.addEnvironment(configurableApplicationContext.getEnvironment(),
"spring.datasource.url=" + mssqlServerContainer.getJdbcUrl(),
"spring.datasource.username=" + mssqlServerContainer.getUsername(),
"spring.datasource.password=" + mssqlServerContainer.getPassword()
);
}
}

As it came out after a more rigorous search I can use withInitScript and define the schema plus data into init.sql, make sure that db/init.sql resides into test/resources folder.
It works as below:
public MSSQLServerContainer mssqlServerContainer = (MSSQLServerContainer) new MSSQLServerContainer()
.withInitScript("db/init.sql")
.withExposedPorts(1433);
In this case, you don't need Flyway.

you have two options:
create container from dockerfile Links : testcontainer dockerfile Customize your MySQL Database in Docker
you can use a database migration tools like flyway and database will automatically populated

When I worked with a similar setup I've implemented the following approach:
I've managed the schema in Flyway which is integrated with spring boot anyway. All the migrations were Schema definition related migrations and did not do anything to the data.
I've placed them in src/main/sesources/... (not the test classpath) - because they were also relevant for the regular application startup.
I've created special migrations for tests that populate the test data. These migrations were under src/test/resources of my module.
These "test" migrations were written in a way that they've always started after the regular migrations.
I've configured Flyway to start before the test so that it has populated the test data.
After all, the main idea is to decouple the code of test from the data generation.
It worked for us - so I can recommend this approach.
Having said that, if you're using spring, there are other ways:
Add the data "manually" in a setup if you're using JPA. Place the call to this
code before tests kick in.
Use #Sql annotation to run a script with test data. All-in-all there are many options there, read this tutorial for more information

Related

how to prevent jdbc from trying to connect to a mysql database during unit testing

I'm making an application for a school project, but I'm running into the issue that when I try to run the unit tests that it tries to connect to the database while starting up the application, which isn't required for the tests (because it will be mocked), and is not available in the CI/CD pipeline.
jdbc connection error
I'm building my project in Java Maven Springboot and would like to know how I can prevent it from trying to connect to the database when running my test.
here is a link to my repository: https://gitlab.com/kwetter_jack/Kwetter_posts/-/tree/ci_cd_setup
Your test classes have #SpringBootTest annotation which will start a Spring application context - as your application uses a database the tests will also try to setup and use a database connection.
The simplest solution is to remove the annotation so the tests no longer try to connect to a database. You'll probably need to mock some more dependencies as a result as Spring is no longer creating these for you. You could also have a look at https://www.baeldung.com/spring-boot-testing for some other ideas how you could alter your tests.
Alternatively if you do want / need the application context to run you can add a application.yaml for the tests that defines and uses a in memory DB so the tests have something to connect to - see https://www.baeldung.com/spring-boot-h2-database for details how to do this.
Just change value under spring.datasource to H2 database to prevent
The application connect the real database.
Test application.yml
FYI, You no need to copy all config from original application.yml, just only some config that you need to override.
while I was investigating the spring boot H2 in-memory database (as suggested by Chris Olive and Paranaaan) I also came across the option of using a test container. after looking into this I saw that this enables the project to create a temp docker container with a MySQL image that I can use during the testing of my project, considering I was planning on using docker anyway for the integration testing of my microservices project I attempted this and it worked as I had hoped it would.
if anyone is interested in the test container solution that I used, the information can be found here:
https://www.testcontainers.org/modules/databases/mysql/

Only overriding values with src/test/resources/application.properties for Integration test

I have an integration test that spins up a running copy of my Spring Boot rest api and performs tests with rest template. Currently I can use src/test/resources/application.properties to specify/override any properties I want to set for testing. The problem is that this doesn't only act as overriding, I have to duplicate all of the properties in src/main/resources/application.properties as well. Ideally I would like it to read both files and use the test properties to override any properties found in both files. This works for profile specific properties such as when using both src/main/resources/application.properties and src/main/resources/application-dev.properties, but doesn't seem to be the case for running tests. I know I can use a custom named properties file and pull that in with an annotation but I'd like to avoid that route to keep things clean.
I'm using Spring Boot 2.1.0.RELEASE

Spring Boot: run liquibase migrations without starting app

In Spring Boot, the documentation seems to encourage running migrations on app startup.
This is fine, but sometimes app startup may have side effects / dependencies that I don't want to bother with - I just want to run the migrations on their own. Think just setting up a local dev database, to poke around in it, without even running the app.
In Dropwizard by comparison, running migrations alone is straightforward with built in arguments for the app, like so
java -jar hello-world.jar db migrate helloworld.yml
Is there anything equivalent for Spring Boot? Or do I just have to drop down and run liquibase directly?
I'm interested in a direct answer, but also kind of interested in seeing if I'm misunderstanding something at a higher level - like perhaps this approach of running on startup is generally 'better' for some reasons I haven't discovered yet, so you're encouraged solely to do it this way by Spring Boot as a design choice.
You can use different Spring profiles: For instance, use a profile called 'init' which will activate the 'liquibase' profile.
application.yml: (disable Liquibase by default)
spring:
liquibase:
enabled: false
application-init.yml: (does not run a web container, so spring will close automatically after startup)
spring:
profiles:
include: liquibase
main:
web-application-type: none
spring-liquibase.yml: (enables liquibase)
spring:
liquibase:
enabled: true
change-log: classpath:/db/changelog/changelog.xml
This setup allows you to run Liquibase as an init container (spring.profiles.active=init), but if you want to you can still run Liquibase as part of your web-app (spring.profiles.active=liquibase).
I know this is an old question, but in case someone else stumbles upon it, this might be useful.
You can define a command line argument for your app, that you will use to only spin up a portion of the app context that will run migration.
Here's an example in Kotlin:
import org.springframework.boot.ApplicationContextFactory.ofContextClass
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Import
#SpringBootApplication
class Application
#Import(DataSourceAutoConfiguration::class, LiquibaseAutoConfiguration::class)
class LiquibaseInit
fun main(args: Array<String>) {
if (args.contains("dbinit")) {
SpringApplicationBuilder(LiquibaseInit::class.java)
.contextFactory(ofContextClass(AnnotationConfigApplicationContext::class.java))
.run(*args)
return
}
runApplication<Application>(*args)
}
Note: with Spring Boot 2.4.0 and earlier use SpringApplicationBuilder#contextClass instead of contextFactory.
We have 2 classes declared here: Application (the main app class having #SpringBootApplication annotation) and LiquiBaseInit (having #DataSourceAutoConfiguration and #LiquibaseAutoConfiguration), the first one will spin up the whole context, while the latter will only spin up the beans necessary for Liquibase to run migration.
Inside the main function we check if the arguments array has a string dbinit and if it is there we start an application context out of LiquiBaseInit class.
Now you can run migration with your jar file like this:
java -jar hello-world.jar dbinit
If you're going to run your app in Kubernetes, you might also want to check out this article in my blog: https://blog.monosoul.dev/2021/12/26/using-liquibase-with-kubernetes/ .
This answer mentions a hook that runs after Liquibase. In that question, it was used to populate the database, probably with test or default values.
#Bean
#DependsOn("liquibase")
public YourBean yourBean() {
return new YourBean();
}
static class YourBean {
#PostConstruct
public void shutdown() {
// Exit your application here.
);
}
}
That could work. I don't know, you'd probably even have access to the liquibase mode and only shut down when it was "create."
In general, it might be just less work to run migrations on app start, especially if you're using Spring Boot and its all set up for you. You can then just stop the app.
However if you really would like to run migrations without the Spring Boot app, you can use the Liquibase Gradle and Maven plugins:
https://github.com/liquibase/liquibase-gradle-plugin
https://docs.liquibase.com/tools-integrations/maven/home.html
This will require you to set up the database credentials in another config file, in addition to your application config, so that the plugins can connect to the database.
For this purpose we created our own script that allows not only to gen migrations but also to manually apply them, however it could be applied only from gradle, see:
https://github.com/Wissance/SpringUu
to apply update we use following command:
.\gradlew.bat update -PrunList='changesApply'
to specify db connection edit gradle section changesApply
For more details about our tool see article:
https://m-ushakov.medium.com/code-first-with-spring-boot-hibernate-and-liquibase-48f5c9998d95
U can set up and configure maven liquibase plugin:
https://docs.liquibase.com/tools-integrations/maven/commands/home.html
It allows to use migrations without starting spring boot app itself
can be set up as part of building artifact or like separate task

How to add the mode=mysql to embedded H2 DB in Spring Boot 1.4.1 for #DataJpaTest?

I have some problems with using a schema.sql file to create my sql schema when executing a junit test while this schema contains mysql specific expression. I have to add the mode=mysql to the H2 url.
For example something like this:
jdbc:h2:mem:testd;MODE=MYSQL
But Spring boot automatically uses the url defined in the enum
org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection with its url
jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE.
I have tried similiar approaches to get this to work, but spring does not take the spring.datasource.url=jdbc:h2:mem:testdb;MODE=MYSQL from my test-application.properties. All other settings from my test-application.properties have been read successfully.
If I let spring/hibernate create the schema (without the schema.sql file) with the javax.persistence annotations in my entities everything works fine.
Is there a simple way to add a mode?
Set
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL
in application-test.properties, plus
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ActiveProfiles("test")
on the test class
I was having this same issue. It would not pick up the url when running tests. I'm using flyway to manage my scripts. I was able to get all of these working together by following these few steps.
Created a V1_init.sql script in src/test/resources/db/migration so that it is the first script run by flyway.
SET MODE MYSQL; /* another h2 way to set mode */
CREATE SCHEMA IF NOT EXISTS "public"; /* required due to issue with flyway --> https://stackoverflow.com/a/19115417/1224584*/
Updated application-test.yaml to include the schema name public:
flyway:
schemas: public
Ensure the test specified the profile: #ActiveProfiles("test")
I have tried similiar approaches to get this to work, but spring does not take the spring.datasource.url=jdbc:h2:mem:testdb;MODE=MYSQL from my test-application.properties
Did you try to append this parameters instead of rewriting the existing ones?
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL
All other settings from my test-application.properties have been read successfully.
I thought that file should be named application-test.properties.
I was able to run it with this config:
# for integration tests use H2 in MySQL mode
spring.datasource.url=jdbc:h2:mem:testdb;DATABASE_TO_LOWER=TRUE;MODE=MySQL;
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
The main trick here is to force Hibernate to generate SQL scripts for MariaDB dialect because otherwise Hibernate tries to use H2 dialect while H2 is already waiting for MySQL like commands.
Also I tried to use more fresh MariaDB103Dialect for MariaDB 10.3 but it doesn't worked properly.
You need to set MYSQL mode on h2 and disable replacing of datasource url for embedded database:
Modify application-test.yaml
spring:
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MYSQL
test:
database:
replace: NONE

Use embedded database for test in spring boot

I have a spring boot application, it has a couple of #Entity classes and #RepositoryRestResource repositort interfaces for them. Now I want to write some tests, where I can check that I can add a new record into my database using those repositories, but I don't want to use my configured MySQL database for it, but instead I want to use some embedded db like H2. At the moment I have an application.properties file, which looks like this:
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=qwerty123
Question: How do I configure my app to use other db for tests? I have no xml in my project, everything is based on annotations. I tried to define #Configuration class with #Bean to create DataSource and then use it with #ContextConfiguration annotation on test class, but it says that it can't load context.
If you are using a Maven project, you can add a application.properties file into your src/test/resources, for example with the following content.
# Create DDL
spring.jpa.hibernate.ddl-auto=create
# H2 in local file system allowing other simultaneous connections
spring.datasource.url=jdbc:h2:~/test;AUTO_SERVER=TRUE
Also, you need to include H2 as dependency (pom.xml):
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.193</version>
</dependency>
Spring Boot provides 2 magic annotations related to JPA autoconfigs for tests: #DataJpaTest and #AutoConfigureTestDatabase.
The javadoc says:
By default, tests annotated with #DataJpaTest will use an embedded
in-memory database (replacing any explicit or usually auto-configured
DataSource). The #AutoConfigureTestDatabase annotation can be used to
override these settings.
If you are looking to load your full application configuration, but
use an embedded database, you should consider #SpringBootTest combined
with #AutoConfigureTestDatabase rather than this annotation.
So, the only thing you absolutely need is a dependency in your pom file:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
That's it. However, spring boot spec also has 2 useful caveats:
You need not provide any connection URLs. You need only include a
build dependency to the embedded database that you want to use.
If you are using this feature in your tests, you may notice that the
same database is reused by your whole test suite regardless of the
number of application contexts that you use. If you want to make sure
that each context has a separate embedded database, you should set
spring.datasource.generate-unique-name to true.
And another one:
If, for whatever reason, you do configure the connection URL for an
embedded database, take care to ensure that the database’s automatic
shutdown is disabled. If you use H2, you should use
DB_CLOSE_ON_EXIT=FALSE to do so. If you use HSQLDB, you should ensure
that shutdown=true is not used. Disabling the database’s automatic
shutdown lets Spring Boot control when the database is closed, thereby
ensuring that it happens once access to the database is no longer
needed.
That's almost all you need to know about Spring Boot and embedded DBs. I see absolutely no reason to use the scope of dependency other than test, unless you actually intentionally configure an embedded DB for your application runtime. Believe it or not H2 jar alone takes 1.8M inside your fat jar. In the world on granular microservices, serverless and lambda functions it does matter what you put inside your apps.
I would also recommend checking the options in #AutoConfigureTestDatabase. I use it with #SpringBootTest, but it can also be used with some other annotations, namely #DataJpaTest, both mentioned above:
You will need to use Spring Profiles - https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html#howto-set-active-spring-profiles
You will define an active profile using "spring.profiles.active = development" and then including H2 in your development profile.
The examples use YAML, but they work in standard properties files as well.

Categories

Resources