I have some integration tests, for which I am using Testcontainers. But I have suddenly realized that when my docker container with database for my application is down, all the other tests (excluding the integration tests using Testcontainers) are failing (even the contextLoads() test generated by Spring Boot initializr)
I get:
java.lang.IllegalStateException: Failed to load ApplicationContext at
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'liquibase' defined in class path
resource
[org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]:
Invocation of init method failed; nested exception is
liquibase.exception.DatabaseException:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications
link failure
It is obvious that the application wants to connect to the database, and the database container is down.
I've been investigating, but I don't remember ever needing to start a container just for the test/build process of an application, so this problem is new for me. But if there is something done wrong, it could be here, in my AbstractDatabaseIT class:
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = AbstractDatabaseIT.DockerMySqlDataSourceInitializer.class)
#Testcontainers
public abstract class AbstractDatabaseIT {
private static final String MYSQL_IMAGE_NAME = "mysql:5.7.24";
public static final MySQLContainer<?> mySQLContainer = new MySQLContainer<>(MYSQL_IMAGE_NAME);
static {
mySQLContainer.start();
}
public static class DockerMySqlDataSourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(#NotNull ConfigurableApplicationContext applicationContext) {
Map<String, String> parameters = new HashMap<>();
parameters.put("command", "--character-set-server=utf8");
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"spring.datasource.url=" + mySQLContainer.getJdbcUrl(),
"spring.datasource.username=" + mySQLContainer.getUsername(),
"spring.datasource.password=" + mySQLContainer.getPassword()
);
mySQLContainer.setParameters(parameters);
}
}
}
The integration test extend this class:
public class ChallengeIT extends AbstractDatabaseIT {
#Autowired
private ChallengeRepository repository;
// tests here
All the other, non-integration classes have #SpringBootTest annotation, and the dependencies injected using #Autowired (maybe this is a problem here?)
#SpringBootTest
class EthMessageVerifierTest {
#Autowired
private EthMessageVerifier ethMessageVerifier;
// tests here
What am I missing here? I remember seeing the H2 database dependency all around many projects. Should I drop the testcontainers in favour of H2? Or can I somehow create a single testcontainer instance for all the other tests?
Tests that you annotate with #SpringBootTest try to populate the entire Spring context. This includes all your beans: your web layer, your business logic, your database setup, etc.
Hence all the infrastructure (e.g. messaging queues, remote systems, databases) that you need otherwise to run your entire application also needs to be present for such tests.
So #SpringBootTest also indicates an integration test and you need to provide your database setup as on application start, Spring Boot's auto-configuration tries to configure your DataSource.
For more information, consider this article on #SpringBootTest and this general overview about unit & integration testing with Spring Boot. You don't always have to use #SpringBootTest and can also use one of Spring Boots many test slice annotations to test something in isolation.
Related
I have 2 parent test classes:
#SpringBootTest(properties = {
"spring.datasource.url=jdbc:tc:mysql:8.0.25:///my_test_db?TC_INITSCRIPT=db/init_mysql.sql",
"spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver"
})
#TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
public abstract class UserApplicationIntegrationTest {
}
and
#SpringBootTest
#TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
public abstract class UserApplicationTest {
}
The idea is for various test classes to extend these classes. The ones which require a mocked MySQL DB will extend UserApplicationIntegrationTest. Ones which don't need a DB connection but that do require a Spring context will extend UserApplicationTest.
In the absence of UserApplicationIntegrationTest, all the test classes extending UserApplicationTest work well, including using the Mockito framework. Unfortunately, when I introduce UserApplicationIntegrationTest and its sub-tests (which work perfectly with the dockerised db instance), these tests begin to fail as they suddenly demand a datasource.
Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class
If I try excluding datasource auto-configuration either in app properties or in annotations of the parent class, the testcontainers tests (those extending UserApplicationIntegrationTest) start failing because of a problem with the Spring context and not being able to autowire beans any longer in those tests.
Before I know it, I'm down a rabbit hole of attempting messy exclusions/additions that I've been down before in previous projects and it only leads to problems further down the line.
Essentially I want 3 types of tests coexisting in my project:
Unit tests with no Spring context
Unit tests with a Spring context (including lots of mocking but still autowiring/constructor injection support)
Integration tests with a Spring context that spin up testcontainers and allow me to test DB interactions (and potentially end to end tests to come)
The original reason that I wanted to avoid launching testcontainers for all Spring context tests (which would 'work' perfectly well and only include 1 docker delay in the build process) was because it was irritating me to have to wait for the mysql connection to the dockerised instance every time I ran individual Spring context tests locally during development.
Is there a tidy way to achieve this or an altogether better way of navigating the requirement?
Thanks in advance.
Hopefully I understand you right, what I did was implementing an abstract TestContainer test class:
package de.dwosch.it;
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = AbstractPostgreSQLTestContainerIT.Initializer.class)
#Testcontainers
public abstract class AbstractPostgreSQLTestContainerIT {
private static final String POSTGRES_VERSION = "postgres:11.1";
public static PostgreSQLContainer database;
static {
database = new PostgreSQLContainer(POSTGRES_VERSION);
database.start();
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
configurableApplicationContext,
"spring.datasource.url=" + database.getJdbcUrl(),
"spring.datasource.username=" + database.getUsername(),
"spring.datasource.password=" + database.getPassword()
);
}
}
}
Then I just extend my test classes by this abstract class which will fire up a test container and the whole spring context for better separation
class MyAwesomeControllerIT extends AbstractPostgreSQLTestContainerIT { }
It seems that #WebMvcTest and #MockBean are not working as expected. Maybe I'm missing something... I have a controller with some dependencies I'm mocking with #MockBean, but the application fails to start because it cannot find another bean that I think should not be required in this case.
CONTROLLER:
#RestController
public class ExchangeRateStoreController {
private AddExchangeRate addExchangeRate;
private AddExchangeRateRequestAdapter addExchangeRateRequestAdapter;
private GetExchangeRate getExchangeRate;
private GetExchangeRateRequestAdapter getExchangeRateRequestAdapter;
#Autowired
public ExchangeRateStoreController(ExchangeRateRepository exchangeRateRepository, ExchangeRateDateValidator exchangeRateDateValidator, ExchangeRateView exchangeRateView) {
addExchangeRate = new AddExchangeRate(exchangeRateRepository, exchangeRateDateValidator);
addExchangeRateRequestAdapter = new AddExchangeRateRequestAdapter();
getExchangeRate = new GetExchangeRate(exchangeRateView);
getExchangeRateRequestAdapter = new GetExchangeRateRequestAdapter();
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody AddExchangeRateRequest addExchangeRateRequest) {
addExchangeRate.execute(addExchangeRateRequestAdapter.toCommand(addExchangeRateRequest));
}
}
TEST:
#RunWith(SpringRunner.class)
#WebMvcTest(ExchangeRateStoreController.class)
public class ExchangeRateStoreControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
ExchangeRateRepository exchangeRateRepository;
#MockBean
ExchangeRateDateValidator exchangeRateDateValidator;
#MockBean
ExchangeRateView exchangeRateView;
#Test
public void givenValidExchangeRateCommand_whenCreate_thenOK() throws Exception {
String validRequestBody = "{\"from\":\"EUR\",\"to\":\"USD\",\"amount\":1.2345,\"date\":\"2018-11-19\"}";
doNothing().when(exchangeRateDateValidator).validate(any());
doNothing().when(exchangeRateRepository).save(any());
mvc.perform(post("/").content(validRequestBody).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());
}
APPLICATION:
#SpringBootApplication
#EnableJpaRepositories("com...exchangerate.store.infrastructure.persistence")
#EntityScan("com...exchangerate.store.infrastructure.persistence")
#ComponentScan(basePackages = {"com...exchangerate.store.infrastructure", "com...exchangerate.store.application"} )
public class ExchangeRateStoreApplication {
public static void main(String[] args) {
SpringApplication.run(ExchangeRateStoreApplication.class, args);
}
}
And the error I get when run the test:
APPLICATION FAILED TO START
Description:
A component required a bean named 'entityManagerFactory' that could
not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your
configuration.
But, as you can see, entityManagerFactory is not a controller's dependency. So, why is the test trying to load this bean? I'm mocking all the controller dependencies, so I think it shouldn't do this.
The problem's caused by your use of #EnableJpaRepositories on your application's main class. By placing it on the main class, you're indicating that JPA repositories must always be enabled, irrespective of which particular slice of functionality you're trying to test.
You can fix your problem by doing one of the following:
Move #EnableJpaRepositores and #EntityScan onto a separate JPA-specific configuration class
Remove #EnableJpaRepositories and #EntityScan and rely on the auto-configured defaults. For this to work, your repositories and entities will have to be in a sub-package of your main class's package.
There's some more information about this in Spring Boot's reference documentation where it says the following:
If you use a test annotation to test a more specific slice of your application, you should avoid adding configuration settings that are specific to a particular area on the main method’s application class.
In this particular case, the configuration setting that is specific to a particular area is #EnableJpaRepositories.
Hello there is an excellent link on this post: EntityManagerFactory not found in SpringBoot
You could check your spring boot jpa integration and have some good tips to set up your environment.
I have an application using SpringBoot2 with mongodb and I am trying to test json serialization of some DTOS by making tests like:
#JsonTest
#RunWith(SpringRunner.class)
public class SomeDTOTest {
#Autowired
JacksonTester < SomeDTO > json;
#Test
public void someTest() {}
}
However underneath spring is trying to create repository bean and giving me informations:
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean named 'mongoTemplate' that could not be found.
Action:
Consider defining a bean named 'mongoTemplate' in your configuration.
I have more integration test that is using the repository and are annotated with #SpringBootTests and they are working fine...
Is there a way of restricting spring to only creating JacksonTester bean?
You could just create a test without spring runner.
This is an example example test
When loading the spring context if there is an autowired annotation of a mongotemplate somewhere spring will try to provide it. You might consider:
Provided mongo template in tests
Try using #DataMongoTest which will provide an embedded database.
Set an Autowired not required
Use #Autowired(required= false)
Mock mongotemplate
Use #MockBean annotation in order to mock mongoTemplate
I found it quite challenging to have both Integration tests as well as Unit tests in a Spring Boot application.
I checked Spring website and tried many solutions. The one that worked for me was to exclude the AutoConfiguration classes:
#RunWith(SpringRunner.class)
#JsonTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class JsonTests {
#Autowired
private JacksonTester json;
#MockBean
private MyRepository repository;
#MockBean
private MongoTemplate mongoTemplate;
#Test
public void someTest() {}
}
You can find a complete Spring Boot application that include Integration and Unit tests here.
I have a spring boot application that fires up and executes a class that listens to Application Ready event to call an external service to fetch some data and then use that data to push some rules to the classpath for execution. For local testing we have mocked the external service within our application which works fine during the application startup.
The issue is while testing the application by running it with spring boot test annotation and embedded jetty container either on :
RANDOM PORT
DEFINED PORT
In case of RANDOM PORT, at the application startup, it picks up the url for the mock service from the properties file at a defined port and has no clue where the embedded container is running since it is randomly picked up, hence failing to give response.
In case of DEFINED PORT, for the first test case file it runs successfully, but the moment next file is picked up, it fails saying the port is already in use.
The test cases are partitioned logically in multiple files and need
the external service to be called before the container starts to load
the rules.
How can I either share the embedded container between test files in case of using defined port or refactor my application code instead to get hold of the random port while starting up during the test case execution.
Any help would be appreciated.
Application Startup code :
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Autowired
private SomeService someService;
#Override
public void onApplicationEvent(ApplicationReadyEvent arg0) {
try {
someService.callExternalServiceAndLoadData();
}
catch (Execption e) {}
}
}
Test Code Annotations: Test1
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
public class Test1 {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void tc1() throws IOException {.....}
Test Code Annotations: Test2
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
public class Test2 {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void tc1() throws IOException {.....}
If you insist on using the same port on multiple test, you can prevent spring from caching the context for further tests by annotating your testclass with: #DirtiesContext
In your case:
#RunWith(SpringRunner.class)
#DirtiesContext
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
Here is a quote from Andy Wilkinson from his answer on this discussion
This is working as designed. Spring Framework's test framework will, by default, cache contexts for possible reuse by multiple test classes. You have two tests with different configuration (due to #TestPropertySource) so they will use different application contexts. The context for the first test will be cached and kept open while the second test is running. Both tests are configured to use the same port for Tomcat's connector. As a result, when the second test is run, the context fails to start due to a port clash with the connector from the first test. You have a few options:
Use RANDOM_PORT
Remove #TestPropertySource from Test2 so that the contexts have identical configuration and the context from the first test can be reused for the second test.
Use #DirtiesContext so that the context isn't cached
I ran across the same issue. I know this question is a little old, but this may be of assistance:
Tests that use #SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) can also inject the actual port into a field by using the #LocalServerPort annotation, as shown in the following example:
Source: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-user-a-random-unassigned-http-port
The code example given is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {
#Autowired
ServletWebServerApplicationContext server;
#LocalServerPort
int port;
// ...
}
in application.properties
server.port=0
will run the application in random ports
I have spring bean class RedisRepo
inside I am initialising my database connection with #PostConstruct:
#PostConstruct
public void init() {
logger.debug("RedisRepo, Init.");
client = new RedisClient(REDIS_HOST, REDIS_PORT);
...
}
I am creating this bean using java config at SpringConfiguration.class:
#Bean
#Scope("singleton")
public RedisRepo redisjRepo() {
return new RedisRepo();
}
I started to build Integration tests using Spring.
I am using the same configuration class (SpringConfiguration.class) for tests:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringConfiguration.class)
My Test class using embedded-redis so I need to init it right before I start my tests:
#Before
public void init() throws IOException {
//init embedded-redis
}
The problem is when I start the tests the #PostConstruct of the RedisRepo class executed before my integration-test init() class (past below) which leading me to null since my embedded redis hasnt initialised just yet.
How could I avoid it?
Mybe I am not doing something right?
Thanks,
ray.
I would suggest to consider using spring Boot auto-configuration (#EnableAutoConfiguration or #SpringBootApplication) to initialize Redis connection. You can use these Spring Boot properties to customize Redis:
# REDIS (RedisProperties)
spring.redis.database= # database name
spring.redis.host=localhost # server host
spring.redis.password= # server password
spring.redis.port=6379 # connection port
spring.redis.pool.max-idle=8 # pool settings ...
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.sentinel.master= # name of Redis server
spring.redis.sentinel.nodes= # comma-separated list of host:port pairs
This would remove need for connection initialization in #PostConstruct production code and you can just autowire your Redis related beans from context dusting testing.
EDIT1:
To populate Redis instance before testing method you can use #BeforeMethod (using TestNg) or #Before (using JUnit). To populate it before the test but after the context is initialized use #PostConstruct in test class.
EDIT2:
You asked on generic rule how to overcome need for initialization of resources in #PostConstruct. I believe your problem is how you are wiring beans in your application.
Your #PostConstruct initialization is done in other bean, where RedisClient is stored as variable client. I would argue that it most probably mixing of concerns. If you register RedisClient bean into spring context this way:
#Bean
public RedisClient redisClient() {
RedisClient client = new RedisClient(REDIS_HOST, REDIS_PORT);
...
return client;
}
You can just autowire it into bean where you had #PostConstruct initialization. You are also able to autowire it during test. If RedisClient is not thread-safe, you may want to consider prototype or request scope for it.
With this approach, I extremely rarely use #PostConstruct and use Spring IoC container to handle all the reusable resource instances.