Cucumber 4 with Spring Boot and JPA - spring

I am using cucumber + cucumber-spring 4.1 for testing a SpringBoot 2.1 application. This has scenarios that require the database to be rolled back between each scenario, but for the life of me cannot get it to work.
I have tried:
A base class with several annotations
`
#Transactional
#RunWith(SpringRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public abstract class SpringBootBaseIntegrationTest { .. } `
Stepdef classes like:
`
#ContextConfiguration(classes = {Application.class})
#Ignore
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#Transactional
public class DatabaseSteps extends SpringBootBaseIntegrationTest implements En { ... }
`
Features nd scenarios are annotated with #txn and the 'cucumber.api.spring' glue is added.
My application has :
`
#EnableJms
#EnableJpaRepositories
#EnableTransactionManagement
#SpringBootApplication
#PropertySource(ignoreResourceNotFound = false, value = "classpath:application.properties")
class Application implements CommandLineRunner { ... }
`
and I use standard JPA repositories.
My application.properties is:
`
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.generate_statistics=false
spring.jpa.properties.hibernate.show_sql=false
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
#spring.h2.console.enabled=true
`
but whatever I do, in any combination, the data still remains between tests.
Can anyone shed light or managed a working set up?
Cheers

I figured it out in the end. My Cucumber tests were entering the app via a number of different methods (JMS, Rest), and consequently the test did not own the transaction scope for data injected via these services.
Consequently, I am truncating the db tables in a before scenario hook for each test which seems to work fine

Related

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.

Spring boot cucumber testing

I try to perform some Cucumber tests of a Spring boot application.
It seems like Spring Boot is not started before tests are running.
What am i missing?
https://bitbucket.org/oakstair/spring-boot-cucumber-example
My Cucumber repo still runs without doing all the above steps:
https://github.com/BarathArivazhagan/Cucumber-spring-integration
Docs : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
To add more:
#SpringBootTest takes care of loading the application context in that case #ContextConfiguration is reductant.
Spring test automatically provides a bean of TestRestTemplate which can be autowired but still it should work with RestTemplate also.
It still runs without RANDOM_PORT but RANDOM port can be also used in conjunction for testing.
Let's say you have a feature file, feature1, and glueCode in org.xyz.feature1
#RunWith(Cucumber.class)
#CucumberOptions(
plugin = {"pretty"},
features = "src/test/java/resources/feature/feature1",
glue = {"org.xyz.feature1"})
public class CucumberTest {
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Application.class},
webEnvironment = WebEnvironment.RANDOM_PORT)
#ContextConfiguration
#Ignore
#Transactional
public class FeatureTest extends CucumberTest {
#LocalServerPort
int randomServerPort;
#Given("........")
public void test_1 {
}
}
I found the problem and has updated the repo.
I did the following to get it working:
Added RANDOM_PORT to #SpringBootTest
Added #ContextConfiguration
Switched from RestTemplate to TestRestTemplate

How do I import configuration classes in a #DataJpaTest in a SpringBootTest?

I have a SpringBoot Application and I a config package with
#Configuration
#EnableJpaAuditing
public class PersistenceConfig {
}
But the PersistenceConfig does not get picked up in a PersonRepositoryTest
#RunWith( SpringRunner.class )
#DataJpaTest
public class PersonRepositoryTest {
// Tests ...
}
However, if I change from #DataJpaTest to #SpringBootTest, PersonRepositoryTest will pick up the config.
My package structure is
- main
- java
- config
PersistenceConfig.java
- domain
Person.java
- persistence
PersonRepository.java
Application.java // #SpringBootApplication
- test
- java
- persistence
PersonRepositoryTest.java
The Testing improvements in Spring Boot 1.4 suggest to test the persistence layer with #DataJpaTest
Observation:
Doing both annotations on the Test class still do not import the config
#SpringBootTest
#DataJpaTest
Question 1:
When testing the Persistence Layer with #DataJpaTest
how do I properly (best practise way in Spring Boot) import the config package into my Tests?
Question 2:
Can it be an acceptable work around using #SpringBootTest? I am aware that #DataJpaTest is also a meta annotation with sensible auto configuration for my database including transaction management. But what If I do not need it?
A solution is to use #Import to import your configuration to the configuration done by #DataJpaTest. This is my understanding of #Import.
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(AuditConfiguration.class)
public class AuditTest {
}
with AuditConfiguration that enables auditing
#Configuration
#EnableJpaAuditing
public class AuditConfiguration {
}
You can try this:
annotate PersistenceConfig with #ComponentScan to enable component scanning in Spring.
#Configuration
#EnableJpaAuditing
#ComponentScan(basePackages = "com.yourbasepackage")
public class PersistenceConfig {
}
With no further configuration, #ComponentScan will default to scanning the same package as the PersistenceConfig class.
And add the #Context-Configuration annotation to tell it to load its configuration from the PersistenceConfig.class.
#RunWith( SpringRunner.class )
#DataJpaTest
#ContextConfiguration(classes=PersistenceConfig.class)
public class PersonRepositoryTest {
// Tests ...
}
After #georges van post I have found out that ALL configuration classes get also picked up by just adding one line in the test:
#RunWith( SpringRunner.class )
#DataJpaTest
#ComponentScan(basePackages = "com.basepackage.config")
public class PersonRepositoryTest {
// Tests ...
}
If someone only wants ONE specific configuration class you can do:
#RunWith( SpringRunner.class )
#DataJpaTest
#ContextConfiguration(classes=MyConfig.class)
public class PersonRepositoryTest {
// Tests ...
}
Or multiple classes with:
#ContextConfiguration(classes={MyConfig1.class, MyConfig2.class})

How to exclude classes that are added using Spring's AutoConfiguration when using #WebMvcTest?

I want to test my Controller using #WebMvcTest and mock the dependencies, but Spring Boot's AutoConfiguration loads my Couchbase (Spring Data) configuration automatically. Couchbase is not available on some platforms which run the test, so the Test class will throw an Exception. How can I exclude some classes from the AutoConfiguration mechanism?
I tried the excludeFilters option of #WebMvcTest and #ActiveProfile to load another Application Context, but that didn't work.
Test configuration:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ExchangeJobImportController.class, excludeFilters = #ComponentScan.Filter(classes = CouchbaseConfiguration.class))
#ActiveProfiles("test")
public class ExchangeJobImportControllerTest {
...
}
Couchbase Configuration:
#Configuration
#EnableCouchbaseAuditing
#EnableConfigurationProperties({CouchbaseProperties.class})
#EnableCouchbaseRepositories(basePackages = "com....")
public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration {
...
}
After some more struggling I found the solution: exclude the #SpringBootApplication annotated class in the #WebMvcTest annotation:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ExchangeJobImportController.class, excludeAutoConfiguration = Application.class, secure = false)
#ActiveProfiles("localhost")
public class ExchangeJobImportControllerTest {
...
}
For the record, I am using Spring Boot 1.5.1.RELEASE
I know it's been some time but I suppose the reason why the #ComponentScan.Filter solution doesn't work is that it looks for an annotation type for the classes field by default.
So, setting the FilterType as ASSIGNABLE_TYPE should work in your case:
#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = CouchbaseConfiguration.class
)

How to exclude/disable a specific auto-configuration in Spring boot 1.4.0 for #DataJpaTest?

I am using the #DataJpaTest from Spring for my test which will then use H2 as in memory database as described here . I'm also using Flyway for production. However once the test starts FLyway kicks in and reads the SQL file. How can I exclude the FlywayAutoConfiguration and keep the rest as described here in spring documentation in order to let Hibernate create the tables in H2 for me?
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private MyRepository triggerRepository;
}
Have you tried the #OverrideAutoConfiguration annotation?
It says it "can be used to override #EnableAutoConfiguration".
I'm assuming that from there you can somehow exclude FlywayAutoConfiguration
like so:
#EnableAutoConfiguration(exclude=FlywayAutoConfiguration.class)
Adding the dependency on an in-memory database to my build.gradle
e.g. testRuntime "com.h2database:h2:1.4.194"
And adding flyway.enabled=false to application.properties in src/test/resources worked for me.
I am converting an old JDBC app into a spring-data-jpa app and I'm working on the first tests now. I kept seeing a security module instantiation error from spring-boot as it tried to bootstrap the security setup, even though #DataJpaTest should theoretically be excluding it.
My problem with the security module probably stems from the pre-existing implementation which I inherited using PropertySourcesPlaceholderConfigurer (via my PropertySpringConfig import below)
Following the docs here:
http://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#test-auto-configuration
and your comments on #LiviaMorunianu's answer, I managed to work my way past every spring-boot exception and get JUnit to run with an auto-configured embedded DB.
My main/production spring-boot bootstrap class bootstraps everything including the stuff I want to exclude from my tests. So instead of using #DataJpaTest, I copied much of what it is doing, using #Import to bring in the centralized configurations that every test / live setup will use.
I also had issues because of the package structure I use, since initially I was running the test which was based in com.mycompany.repositories and it didn't find the entities in com.mycompany.entities.
Below are the relevant classes.
JUnit Test
#RunWith(SpringRunner.class)
#Transactional
#Import({TestConfiguration.class, LiveConfiguration.class})
public class ForecastRepositoryTests {
#Autowired
ForecastRepository repository;
Forecast forecast;
#Before
public void setUp() {
forecast = createDummyForecast(TEST_NAME, 12345L);
}
#Test
public void testFindSavedForecastById() {
forecast = repository.save(forecast);
assertThat(repository.findOne(forecast.getId()), is(forecast));
}
Live Configuration
#Configuration
#EnableJpaRepositories(basePackages = {"com.mycompany.repository"})
#EntityScan(basePackages = {"com.mycompany.entity"})
#Import({PropertySpringConfig.class})
public class LiveConfiguration {}
Test Configuration
#OverrideAutoConfiguration(enabled = false)
#ImportAutoConfiguration(value = {
CacheAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
TransactionAutoConfiguration.class,
TestDatabaseAutoConfiguration.class,
TestEntityManagerAutoConfiguration.class })
public class TestConfiguration {
// lots of bean definitions...
}
PropertySpringConfig
#Configuration
public class PropertySpringConfig {
#Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
throws IOException {
return new CorePropertySourcesPlaceholderConfigurer(
System.getProperties());
}
}
In my particular case, i needed to disable the FlywayDB on in-memory integration tests. These are using a set of spring annotations for auto-configuring a limited applicationContext.
#ImportAutoConfiguration(value = TestConfig.class, exclude = FlywayAutoConfiguration.class)
the exclude could effectively further limit the set of beans initiated for this test
I had the same problem with my DbUnit tests defined in Spock test classes. In my case I was able to disable the Flyway migration and managed to initialize the H2 test database tables like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = ["flyway.enabled=false", "spring.datasource.schema=db/migration/h2/V1__init.sql"])
I added this annotation to my Spock test specification class. Also, I was only able to make it work if I also added the context configuration annotation:
#ContextConfiguration(classes = MyApplication.class)
I resolved the same issue by excluding the autoconfiguration from my application definition, i.e.
#SpringBootApplication(exclude = {FlywayAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
you can also sue the following annotation:
#RunWith(SpringRunner.class)
#DataJpaTest(excludeAutoConfiguration = {MySqlConfiguration.class, ...})
public class TheClassYouAreUnitTesting {
}
You can just disable it in your test yaml file:
flyway.enabled: false

Resources