I got a DataMongoTest which is executing fine but it does not load the interceptors that are configured
#Configuration
public class EntityInterceptors extends AbstractMongoEventListener<Entity> { ... }
#DataMongoTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles({"default", "test"})
public class MongoTest {
#Autowired
private DataRepository repo;
...
}
The repository gets correctly wired and is accessible in the tests. Its not an embedded DB but an external test db that gets cleaned up before tests.
If I execute this inside a application environment the interceptors all load. But they don't load inside the test class. How can I add the class to the execution context without using it as SpringBootTestwhere all the other unneded stuff is also loaded?
DataMongoTest is not enough to run for this:
#ExtendWith(SpringExtension.class)
// Add Config and all dependent Interceptors
#SpringBootTest(classes = {
InterceptorConfig.class,
TopEntityInterceptor.class,
CascadedEntityInterceptor.class
})
// MONGO INIT
#ImportAutoConfiguration
#AutoConfigurationPackage
#AutoConfigureDataMongo
#ActiveProfiles({"default", "test"})
class InterceptorTest {
#Autowired TopEnitityRepository repo;
}
You can now use the repo and check if the interceptors did their job after persisting and if interceptors also cascade.
Related
I use spring boot 3
Main spring boot class
#EnableTransactionManagement
#SpringBootApplication
#Slf4j
public class FlexApplication{
private final ApplicationParameterManager appParamManager;
public FlexApplication(ApplicationParameterManager appParamManager) {
this.appParamManager = appParamManager;
}
#PostConstruct
public void init(){
}
....
}
#Service
#Slf4j
public class ApplicationParameterManager{
....
}
Basic test
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
Otherwise this test, application run well
I get this error
Parameter 0 of constructor in com.acme.FlexApplication required a bean
of type 'com.acme.parameter.ApplicationParameterManager' that could
not be found.
I think it is not related to version of Spring Boot.
As you're using #DataJpaTest , your bean is not created
Spring Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Solution would be to use #SpringBootTest instead of #DataJpaTest if your test is not really a JPA test.
Also, still using #DataJpaTest you could add #Import(ApplicationParameterManager.class) to your test class
When using #DataJpaTest, you are not creating the whole spring context as when you run the application normally but you only create the beans responsible for data access layer.
In order to run your tests properly, you need to provide a mocked bean of type ApplicationParameterManager.
The easiest way to do it is by utilizing #MockBean annotation.
So, to make your tests work, edit the test in the following way.
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#MockBean
private ApplicationParameterManager applicationParameterManager;
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
That way, the spring context will include a mocked bean of your required dependency.
Take a look at #MockBean java doc for more information. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
If you prefer to run the whole spring context in order to perform full integration tests, take a look at #SpringBootTest annotation.
#DataJpaTest should be used when you want to test data access layer in isolation.
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 {
...
}
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.
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;
...
}
I'm using Spring Data JPA domain event as described in https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.domain-events. The event listener is marked with #Service. It is working perfectly when I run it, but I can't make it works when testing it using #DataJpaTest. If I replaced this with #SpringBootTest, the test run perfectly.
I know that #DataJpaTest will not load #Service. But even if I add #Import(MyService.class), this will still not work. My question how do I test domain event with #DataJpaTest without loading the full context as in #SpringBootTest?
It turned out that #SpringBootTest added #Transactional to the test. This causes the domain event listener to be not executed since it is still in transaction.
This is my solution.
// TestConfig
#TestConfiguration
public class TestConfig {
#Bean
public MyService myService() {
return new MyService()
}
}
// Domain Event Test
#RunWith(SpringRunner.class)
#Import({TestConfig.class})
#Transactional
#DataJpaTest
public class DomainEventTest {
#Autowired
private TestRepository repository;
public void domainEventTest() {
Entity entity = new Entity();
repository.save(entity);
}
}