I have a very basic AttributeConverter
implementation that I want to test. As such, I'll first apply a simple hash, but instead of un-hashing I'll just throw an UnsupportedOperationException
.
public class EncryptorConverter implements AttributeConverter<String, byte[]> {
@Override
public byte[] convertToDatabaseColumn(String attribute) {
// Apply hash
}
@Override
public String convertToEntityAttribute(byte[] dbData) {
throw new UnsupportedOperationException();
}
}
And an entity
@Entity
public class Account {
@Id
private String id;
@Column
@Convert(converter = EncryptorConverter.class)
private String sensitiveInfo;
}
And let's say I have a very fundamental repository for Account
:
@Repository
public interface AccountRepository extends JpaRepository<Account, String> {
}
I want to make sure that my EncryptorConverter
is being called in a @DataJpaTest
, like this:
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class})
public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private AccountRepository accountRepository;
public void shouldSaveEncryptedData() throws Exception {
Account expectedAccount = new Account("1", "sensitive info");
accountRepository.save(expectedAccount);
// I was expecting an exceptioon with UnsupportedOperationException as root cause here
Optional<Account> actualAccount = accountRepository.findById("1");
assertTrue(actualAccount.isPresent());
assertEquals(actualAccount.get(), expectedAccount);
}
}
I expected the aforementioned test to fail, but instead it passes. All debug I made suggested that the entity was persisted without any encryption in the DB. I already read this article, which seemed more promising for the problem I'm facing, but it didn't help much.
Does someone know what to do here?
Extra: this is how I'm configuring my test DB:
@EnableJpaRepositories(basePackages = {"..."})
@EnableTransactionManagement
public class TestDatabaseConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource);
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("...");
return lef;
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
The problem isn't the fact that the converter isn't being picked up, the problem is your test and lack of understanding of how Hibernate works.
You are saving the entity, which puts it in the first level cache, next you do a findById
which will result in an EntityManager.find
which will first look into the 1st level cache and as the entity is found no query nor conversion will take place.
To fix, simulate a transaction. For this inject the TestEntityManager
into your class and flush
and clear
after the save
. This will issue the SQL to the database and clear the 1st level cache.
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class})
public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private TestEntityManager tem;
@Autowired
private AccountRepository accountRepository;
@Test
public void shouldSaveEncryptedData() throws Exception {
Account expectedAccount = new Account("1", "sensitive info");
accountRepository.save(expectedAccount);
// Simulate commit/tx end
tem.flush();
tem.clear();
Optional<Account> actualAccount = accountRepository.findById("1");
assertTrue(actualAccount.isPresent());
assertEquals(actualAccount.get(), expectedAccount);
}
}