setting up multiple test containers in spring boot - testcontainers

hi i have the following:
#Testcontainers
#Slf4j
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTestContainerSetup {
#Container
static final PostgreSQLContainer POSTGRES_CONTAINER = new......;
#Container
public static final S3MockContainer s3Container = new....
The above doest work for some reason, the postgresql never gets spun up., if i remove the s3 container outside into its own class and extend this as sub class to the new class. it works fine.. I dont understand why that would be the case.. can one class only have one container annotation?

Related

Unable to inject(autowire) a service when test elasticsearch with testcontainers

So, I want to test elasticsearch with testcontainers. Test containers work well and start good, but when I want to invoke a method from my service this is null, because is not injected, and I don't why.
#Testcontainers
#SpringBootTest
#ActiveProfiles("dev")
public class ArticleServiceTestDemo {
#Autowired
private ArticleService articleService;
private static final String ELASTICSEARCH_VERSION = "7.9.2";
private static final DockerImageName ELASTICSEARCH_IMAGE = DockerImageName
.parse("docker.elastic.co/elasticsearch/elasticsearch")
.withTag(ELASTICSEARCH_VERSION);
#Test
public void myTest() {
try (
ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE);
) {
container.start();
ResponseEntity<Article> article = articleService.persistArticle(new ArticleRequestDto());
assertTrue(article.getStatusCode().is2xxSuccessful());
}
}
}
This is my error:
java.lang.NullPointerException: Cannot invoke "com.ArticleService.persistArticle(com.ArticleRequestDto)" because "this.articleService" is null
When I run a simpe SpringBootTest (without elasticsearch connection and testcontainers) the ArticleService was injected good. But in test with testcontainers this not injected.
Please check ArticleService class is spring bean or not, means ArticleService class was annoted #Service or #Component annotaion or not.
When you apply #Component on a class, then Spring bean container initialize as bean object in Spring Bean Container and Spring Container is trying to inject initialized bean object to annoted #Autowired field. And if you apply #Component on class NullPointerException occurs.
For example this test runs good. And ArticleService was injected.
#SpringBootTest
public class DemoTest {
#Autowired
private ArticleService articleService;
#Test
public void simpleTest() {
articleService.persistArticle(new ArticleRequestDto());
}
}
Can you check if #ActiveProfiles("dev") isn't messing up the beans wiring?
Your example that runs okay doesn't have it. For example remove it and verify if the test runs?
On another note, if you're managing the lifecycle of the testcontainers yourself by calling: container.start(); then you don't need the #Testcontainers annotation which is from JUnit extension for Testcontainers that looks at the static/instance fields marked with #Container and creates & starts/stops these automatically.

How Do I Manually Wire A Spring Boot Integration Test?

Normally, I would test the web layer in a Spring project like this:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SpringBootDemoApplicationTests extends AbstractTestNGSpringContextTests {
#LocalServerPort
int randomServerPort;
#Autowired
private TestRestTemplate restTemplate;
However, I currently have a difficult back end that requires a specific #TestConfiguration class to manually instantiate the test dependencies using beans.
This ultimately means that I can't use the #SpringBootTest annotation as it will try to create conflicting beans and fail to instantiate others.
If I am not using the #SpringBootTest annotation, I can manually create the TestRestTemplate instead of autowiring it, but what do I need to do to start the embedded local server on a random port?
I would still continue using #SpringBootTest, and combine that with using #Profile on your configuration classes.
That way you could have a configuration which is only used during tests, by using #ActiveProfiles on your #SpringBootTest classes. In the same way you can turn other config classes on or off depending on whether you want them to load or not.
For example on your test would have the following
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#ActiveProfiles("unittest")
public class SpringBootDemoApplicationTests extends AbstractTestNGSpringContextTests {
...
}
Then create a configuration class which will instantiate your components the way you want them in test
#Profile("unittest")
#Configuration
public void TestConfiguration {
...
}
And you can use profiles to stop your other configuration class from loading during tests.
#Profile("!unittest")
#Configuration
public void ProdConfiguration {
...
}

How to test repository with junit5 and testcontainers?

I have a sample project in which I experiment with different technologies.
I have the following setup:
Spring Boot 2.3.4.RELEASE
Flyway 7.0.1
Testcontainers 1.15.0-rc2
Junit 5.7.0
How can I test the Repository layer with testcontainer-junit5?
Example of code I have now for CompanyRepositoryTest.java:
#ExtendWith(SpringExtension.class)
#Testcontainers
public class CompanyRepositoryTest {
#Autowired
private CompanyRepository companyRepository;
#Container
public MySQLContainer mysqlContainer = new MySQLContainer()
.withDatabaseName("foo")
.withUsername("foo")
.withPassword("secret");;
#Test
public void whenFindByIdExecuted_thenNullReturned()
throws Exception {
assertEquals(companyRepository.findById(1L), Optional.ofNullable(null));
}
#Test
public void whenFindAllExecuted_thenEmptyListReturned() {
assertEquals(companyRepository.findAll(), new ArrayList<>());
}
}
When I add #SpringBootTest, I need to set up all the context and have some Application load context issues?
The question is, can anyone demystify what #TestContainers annotation does? What is the best practice or correct to use it while testing the Repository?
The JUnit 5 extension provided by the #Testcontainers annotation scans for any containers declared with the #Container annotation, and then starts and stops the those containers for your tests. Containers as static fields will be shared with all tests, and containers as instance fields will be started and stopped for every test.
If you are using Spring Boot, the easiest way to setup testcontainers for your tests is probably to provide properties in application-test.yml. This will use the datasource JDBC URL to launch the testcontainers container. Refer to Testcontainers JDBC support for more information.
You can also test just the repository layer by using #DataJpaTest instead of #SpringBootTest:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ActiveProfiles("test")
class CompanyRepositoryTest { }
Your application-test.yml file:
spring:
datasource:
url: jdbc:tc:mysql:8.0://hostname/databasename
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
In some cases you might also want to use the #TestPropertySource annotation instead:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#TestPropertySource(
properties = {
"spring.datasource.url = jdbc:tc:mysql:8.0://hostname/test-database",
"spring.datasource.driver-class-name = org.testcontainers.jdbc.ContainerDatabaseDriver"
}
)
class CompanyRepositoryTest { }
Please note that the hostname and test-database are not actually used anywhere.
You said
When I add #SpringBootTest, I need to set up all the context and have
some Application load context issues?
If you'd like to try an alternative and Testcontainer is not mandatory you can do it differently.
You do not need to load everyting when using SpringBootTest annotation, you can specify which classes are needed such as
#SpringBootTest(classes = { TheService.class })
or use #Import annotation
and mock others such as
#MockBean
MyService service;
For database connection you can use annotation such as
#ActiveProfiles("my-profile-for-jpa-test")
#DataJpaTest
#EnableJpaAuditing
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
EDIT: I feel like this should be an comment but I wanted to address the SpringBootTest part of the question with proper formatting
Here is an example, how I configured Liquibase (a similar framework to Flyway) with MySql inside Spring:
#DataJpaTest
#TestPropertySource(properties = {"spring.jpa.hibernate.ddl-auto=validate"})
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = { MySqlLiquibaseBaseIT.Initializer.class })
#Testcontainers
public class MySqlLiquibaseBaseIT {
#Container
public static MySQLContainer<?> mysql = new MySQLContainer<>(
DockerImageName
.parse(MySQLContainer.NAME)
.withTag("5.7.22"));
#Configuration
#EnableJpaRepositories
#EntityScan
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mysql.getJdbcUrl(),
"spring.datasource.username=" + mysql.getUsername(),
"spring.datasource.password=" + mysql.getPassword(),
"spring.datasource.driver-class-name=" + mysql.getDriverClassName())
.applyTo(configurableApplicationContext.getEnvironment());
}
#Bean
public SpringLiquibase springLiquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDropFirst(true);
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yml");
return liquibase;
}
}
}
Full MySqlLiquibaseBaseIT.java
As per docs:
The test containers extension finds all fields that are annotated with
Container and calls their container lifecycle methods. Containers
declared as static fields will be shared between test methods. They
will be started only once before any test method is executed and
stopped after the last test method has executed. Containers declared
as instance fields will be started and stopped for every test method.
So in your case it will recreate a container for every test method, it's only responsible for starting and stopping the container. If you need some test data - that has to be done manually, as I see you have Flyway, that should do.
What "context issues" are you talking about?
Repositories are usually not tested separately, you can just test services which run repository methods instead of writing tests for both. If you want to test repos anyway - fill the database with some data in #Before.
If you have more questions please ask.

Cucumber run #SpringBootApplication.main method

I have cucumber working with spring-boot and the context and components are available but #SpringBootApplication.main is not being run so various connections aren't available. Is there a way to make Cucumber invoke the spring-boot main method? This works for the context and components but does not invoke TheApplication.main:
#SpringBootApplication
public class TheApplication {
... connect to stuff
and the steps file:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootApplication
#ContextConfiguration(classes = TheApplication.class, loader = SpringBootContextLoader.class)
#WebAppConfiguration
public class TestCreateSteps {
...
A bit of judicious refactoring solved the issue. Moving connection stuff to the class that needs the connection and using TimeUnit in the test to wait for it to initialise. Test is now working:
#SpringBootTest
#ContextConfiguration()
public class TestCreateSteps {
// Previously controlled by TheApplication
// AccountService now controls itself via application.properties
#Autowired
private AccountService accountService;
...
}

Spring boot Static inner class not initialised when application starts as a jar

When I start up the my spring boot application via intellij, the following class initialised properly and the log can be seen. but if I run agains the jar from the build, MyCurrentTraceContext seem not initialised and I can't see the log in the output either. I do need this class with my customised logic to run put of some argument into MDC. Any advice?
#Configuration
#ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true)
#AutoConfigureBefore(TraceAutoConfiguration.class)
public class MyLogConfiguration extends SleuthLogAutoConfiguration {
private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(WtrLogConfiguration.class);
#Configuration
#ConditionalOnClass(MDC.class)
#EnableConfigurationProperties(SleuthSlf4jProperties.class)
protected static class MySlf4jConfiguration extends Slf4jConfiguration {
#Bean
#ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true)
#ConditionalOnMissingBean
#Override
public CurrentTraceContext slf4jSpanLogger() {
LOGGER.info("************ OVER WRITTING WTIH WtrCurrentTraceContext*******");
return new MyCurrentTraceContext(Slf4jCurrentTraceContext.create());
}
}
}
Please check with
#ComponentScan
in top of your starter class to set the path you need to be scanned while project starts.
In your case that would be :
#ComponentScan("myPackagePath.MyLogConfiguration.MySlf4jConfiguration.*")

Resources