Here we will go through how to use embedded PostgreSQL in you local development environment plus how to use it for DAO integration/unit testing if you have are using PostgreSQL as your production database , you DAO testing should use the same database as your production one to have behavior consistency and same environment precondition.
We will go through a sample spring boot application to how the needed steps :
- The spring boot application dependencies
- DB configuration with embedded PostgreSQL for development
- Customer crud different layers with spring data , mapstruct and lombok
- DAO unit testing with embedded PostgreSQL with custom data population on start
Spring boot dependencies :
the main maven application dependencies will the typical spring boot dependencies (spring data..ect) plus the embedded PostgresSQL and other needed libraries , i will just highlight the embedded PostgresSQL for DB, mapstruct and lombok for DTO and Entity mapping and boilerplate code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>ru.yandex.qatools.embed</groupId> | |
<artifactId>postgresql-embedded</artifactId> | |
<version>2.9</version> | |
</dependency> | |
<dependency> | |
<groupId>org.postgresql</groupId> | |
<artifactId>postgresql</artifactId> | |
<scope>runtime</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.mapstruct</groupId> | |
<artifactId>mapstruct-jdk8</artifactId> | |
<version>1.2.0.Final</version> | |
</dependency> | |
<dependency> | |
<groupId>org.projectlombok</groupId> | |
<artifactId>lombok</artifactId> | |
<optional>true</optional> | |
</dependency> |
Full maven pom.ml is located into the project Github
DB configuration with embedded PostgreSQL for development
So now how to configure the data source to point to the embedded PostgresSQL run-time instance locally , the spring configuration will be as the following :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* the db spring configuration to use in production , to be replaced with actual production configuration , that is for local run only | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
public class DbConfig { | |
private static final List<String> DEFAULT_ADDITIONAL_INIT_DB_PARAMS = Arrays | |
.asList("–nosync", "–locale=en_US.UTF-8"); | |
/** | |
* @param config the PostgresConfig configuration which will be used to get the needed host, port.. | |
* @return the created DB datasource | |
*/ | |
@Bean | |
@DependsOn("postgresProcess") | |
public DataSource dataSource(PostgresConfig config) { | |
DriverManagerDataSource ds = new DriverManagerDataSource(); | |
ds.setDriverClassName("org.postgresql.Driver"); | |
ds.setUrl(format("jdbc:postgresql://%s:%s/%s", config.net().host(), config.net().port(), config.storage().dbName())); | |
ds.setUsername(config.credentials().username()); | |
ds.setPassword(config.credentials().password()); | |
return ds; | |
} | |
/** | |
* @return PostgresConfig that contains embedded db configuration like user name , password | |
* @throws IOException | |
*/ | |
@Bean | |
public PostgresConfig postgresConfig() throws IOException { | |
// make it readable from configuration source file or system , it is hard coded here for explanation purpose only | |
final PostgresConfig postgresConfig = new PostgresConfig(Version.V9_6_8, | |
new AbstractPostgresConfig.Net("localhost", Network.getFreeServerPort()), | |
new AbstractPostgresConfig.Storage("test"), | |
new AbstractPostgresConfig.Timeout(), | |
new AbstractPostgresConfig.Credentials("user", "pass") | |
); | |
postgresConfig.getAdditionalInitDbParams().addAll(DEFAULT_ADDITIONAL_INIT_DB_PARAMS); | |
return postgresConfig; | |
} | |
/** | |
* @param config the PostgresConfig configuration to use to start Postgres db process | |
* @return PostgresProcess , the started db process | |
* @throws IOException | |
*/ | |
@Bean(destroyMethod = "stop") | |
public PostgresProcess postgresProcess(PostgresConfig config) throws IOException { | |
PostgresStarter<PostgresExecutable, PostgresProcess> runtime = PostgresStarter.getDefaultInstance(); | |
PostgresExecutable exec = runtime.prepare(config); | |
PostgresProcess process = exec.start(); | |
return process; | |
} | |
} |
Customer crud different layers with spring data , mapstruct and lombok
for CUSTOMER crud sample , we will have:
- Customer entity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity(name = "customer") | |
@Data | |
@AllArgsConstructor | |
@NoArgsConstructor | |
@Builder | |
public class Customer { | |
@Id | |
private long id; | |
private String name; | |
private String address; | |
private boolean isActive; | |
} |
- Customer Spring data repository
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* main customer repository | |
*/ | |
@Repository | |
@Transactional | |
public interface CustomerRepository extends CrudRepository<Customer, Long> { | |
Optional<Customer> findCustomerByName(String name); | |
} |
- Customer DTO
- Customer Map Struct mapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) | |
public interface CustomerMapper { | |
CustomerDto mapCustomerToDto(Customer customer); | |
Customer mapeDtoToCustomer(CustomerDto customerDto); | |
} |
Again full project code is on Github
DAO unit testing with embedded PostgreSQL with custom data population on start:
- The unit test DB config , here I intended to to make it a little bit detailed as i need to show how you can load only specific entities , DAOs and Data Repository , as unit testing should be scoped and limited to the target DAO layer only and not to load whole application entities and DAOs, from comments and annotations you will understand how to load specific repositories with their entities only
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableTransactionManagement | |
@EnableJpaRepositories(basePackageClasses = {CustomerRepository.class}) | |
@Profile("DaoTest") | |
public class DbConfig { | |
private static final List<String> DEFAULT_ADDITIONAL_INIT_DB_PARAMS = Arrays | |
.asList("–nosync", "–locale=en_US.UTF-8"); | |
/** | |
* @param config the PostgresConfig configuration to use to start Postgres db process | |
* @return PostgresProcess , the started db process | |
* @throws IOException | |
*/ | |
@Bean | |
@DependsOn("postgresProcess") | |
public DataSource dataSource(PostgresConfig config) { | |
DriverManagerDataSource ds = new DriverManagerDataSource(); | |
ds.setDriverClassName("org.postgresql.Driver"); | |
ds.setUrl(format("jdbc:postgresql://%s:%s/%s", config.net().host(), config.net().port(), config.storage().dbName())); | |
ds.setUsername(config.credentials().username()); | |
ds.setPassword(config.credentials().password()); | |
return ds; | |
} | |
/** | |
* @param dataSource the db data source | |
* @return the local entity manager factory bean | |
*/ | |
@Bean | |
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { | |
LocalContainerEntityManagerFactoryBean lcemfb | |
= new LocalContainerEntityManagerFactoryBean(); | |
lcemfb.setDataSource(dataSource); | |
// set the packages to scan , it can be useful if you have big project and you just need to local partial entities for testing | |
lcemfb.setPackagesToScan("io.romeh.postgresembeddeddaotesting.domain", "io.romeh.postgresembeddeddaotesting.dao"); | |
HibernateJpaVendorAdapter va = new HibernateJpaVendorAdapter(); | |
lcemfb.setJpaVendorAdapter(va); | |
lcemfb.setJpaProperties(getHibernateProperties()); | |
lcemfb.afterPropertiesSet(); | |
return lcemfb; | |
} | |
/** | |
* @param localContainerEntityManagerFactoryBean | |
* @return the JPA transaction manager | |
*/ | |
@Bean | |
public JpaTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean) { | |
JpaTransactionManager transactionManager = new JpaTransactionManager(); | |
transactionManager.setEntityManagerFactory(localContainerEntityManagerFactoryBean.getObject()); | |
return transactionManager; | |
} | |
@Bean | |
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { | |
return new PersistenceExceptionTranslationPostProcessor(); | |
} | |
/** | |
* @return the hibernate properties | |
*/ | |
private Properties getHibernateProperties() { | |
Properties ps = new Properties(); | |
ps.put("hibernate.temp.use_jdbc_metadata_defaults", "false"); | |
ps.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect"); | |
ps.put("hibernate.hbm2ddl.auto", "update"); | |
ps.put("hibernate.connection.characterEncoding", "UTF-8"); | |
ps.put("hibernate.connection.charSet", "UTF-8"); | |
ps.put(AvailableSettings.FORMAT_SQL, "true"); | |
ps.put(AvailableSettings.SHOW_SQL, "true"); | |
return ps; | |
} | |
@Bean | |
public PostgresConfig postgresConfig() throws IOException { | |
final PostgresConfig postgresConfig = new PostgresConfig(Version.V9_6_8, | |
new AbstractPostgresConfig.Net("localhost", Network.getFreeServerPort()), | |
new AbstractPostgresConfig.Storage("test"), | |
new AbstractPostgresConfig.Timeout(), | |
new AbstractPostgresConfig.Credentials("user", "pass") | |
); | |
postgresConfig.getAdditionalInitDbParams().addAll(DEFAULT_ADDITIONAL_INIT_DB_PARAMS); | |
return postgresConfig; | |
} | |
@Bean(destroyMethod = "stop") | |
public PostgresProcess postgresProcess(PostgresConfig config) throws IOException { | |
PostgresStarter<PostgresExecutable, PostgresProcess> runtime = PostgresStarter.getDefaultInstance(); | |
PostgresExecutable exec = runtime.prepare(config); | |
PostgresProcess process = exec.start(); | |
return process; | |
} | |
} |
- Then finally the Unit test class:
it shows how to load your test configuration , and how to insert some test data before starting the test case using @sql spring jdbc test annotation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@RunWith(SpringJUnit4ClassRunner.class) | |
@ContextConfiguration(classes = {DbConfig.class}) | |
@ActiveProfiles("DaoTest") | |
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:dao/TestData.sql") | |
public class PostgresEmbeddedDaoTestingApplicationTests { | |
@Autowired | |
private CustomerRepository customerRepository; | |
@Test | |
public void contextLoads() { | |
customerRepository.save(Customer.builder() | |
.id(new Random().nextLong()) | |
.address("brussels") | |
.name("TestName") | |
.build()); | |
Assert.assertTrue(customerRepository.findCustomerByName("TestName") != null); | |
} | |
} |
Hopefully this help to understand how to do DAO unit testing with custom test-data load and using embedded PostgreSQL
References :
- Embedded PostgreSQL :https://github.com/yandex-qatools/postgresql-embedded
- Map struct : http://mapstruct.org/
- Project sample code : https://github.com/Romeh/springboot-postgres-embedded-dao-testing
One comment