Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@EntityListeners Injection + jUnit Testing

I use @EntityListeners to make operations before I save in my Db and after I load. Inside my Listener class I make a call to an Ecryptor (which needs to fetch info from configuration file), so the encryptor can't be called statically and need to be injected in my Listener. Right?

Well, injections in EntityListeners can't be done straight away, but you have some methods to do that, like using SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); or even the method showed here. https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/

Cool, the problem is: None of the solutions support unit testing! When running tests that encryptor I had injected in my model Listener is always null.

Here SpringBeanAutowiringSupport does not inject beans in jUnit tests There is a solution to create this context and pass to a instantiated object, but it does not solve my problem since I have the "Injection" to add to it.

Any way to create a context in my tests and somehow pass it to my listeners? If not, any way I can create a static method to my Encryptor and still have access to the Environment API to read my properties?

Package Listener:

public class PackageListener{
   @Autowired
   Encryptor encryptor;

   @PrePersist
   public void preSave(final Package pack){
      pack.setBic(encryptor.encrypt(pack.getBic()));
   }
   ...

My test

 @Test
 @WithuserElectronics
 public void testIfCanGetPackageById() throws PackageNotFoundException{
     Package pack = packagesServiceFactory.getPackageService().getPackage(4000000002L);
 }

Package service

  public Package getPackage(Long id) throws PackageNotFoundException{
    Package pack = packageDao.find(id);

    if (pack == null) {
        throw new PackageNotFoundException(id);
    }

    return pack;
}

Encryptor:

public class Encryptor{
    private String salt;

    public Encryptor(String salt){
        this.salt = salt;
    }

    public String encrypt(String string){
        String key = this.md5(salt);
        String iv = this.md5(this.md5(salt));
        if (string != null) {
            return encryptWithAesCBC(string, key, iv);
        }
        return string;
    }
    ...
like image 967
Leonardo Beal Avatar asked Dec 01 '17 10:12

Leonardo Beal


People also ask

Is it possible to enable dependency injection inside a JUnit test?

The idea of dependency injection inside our tests is not something new; it is afforded by spring’s test utilities and it’s not that hard to implement for your general case. The following code snippet will show how you can create a custom JUnit 4 Runner to enable field injection inside your tests.

How do I inject a parameter in JUnit 5?

Injecting parameters into your test methods could be done using the JUnit 4 API, but it was fairly limited. With JUnit 5, the Jupiter API can be extended – by implementing ParameterResolver – to serve up objects of any type to your test methods.

How do you name an integration test In JUnit?

JUnit Test. By convention, I’m naming my Integration Test with the suffix of ‘IT’. Traditionally, you will name your unit tests with the suffix of ‘Test’ or ‘Tests’, and your integration tests with the suffix of ‘IT’. This does not affect how JUnit runs the tests.

What is the use of extension In JUnit testing?

Separation of concerns between the unit test and the data that drives it Reuse, should other unit tests require valid Person objects to drive them If the type of parameter is Person, then the extension tells the JUnit platform that it supports that parameter type, otherwise it returns false, saying it does not.


2 Answers

You can create a DemoApplicationContextInitializer class to store the appliationContext reference in a static property in your main class.

public class DemoApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext ac) {
        Application.context = ac;
    }
}


@SpringBootApplication
public class Application {

    public static ApplicationContext context;

    public static void main(String[] args) throws Exception {
        new SpringApplicationBuilder(Application.class)
        .initializers(new DemoApplicationContextInitializer())
        .run(args);
    }
}

Then you can access the context in your entity listener

public class PackageListener{
   //@Autowired
   Encryptor encryptor;

   @PrePersist
   public void preSave(final Package pack){
      encryptor = Application.context.getBean(Encryptor.class);
      pack.setBic(encryptor.encrypt(pack.getBic()));
   }
}

And to make this work in your junit test, just add the initializer in your test like this ...

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT, classes = Application.class)
@ContextConfiguration(classes = Application.class, initializers = DemoApplicationContextInitializer.class)
public class MyTest {
...
}

It works without any issue in my environment. Hope it will be helpful to you too.

like image 69
vsoni Avatar answered Oct 12 '22 21:10

vsoni


To answer what you need, you have to create 2 classes that will do all the configuration needed.

You have to create a testConfig with the next annotations:

@Configuration
@ComponentScan(basePackages = { "yourPath.services.*",
        "yourPath.dao.*" })
@EnableAspectJAutoProxy
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "yourPath.dao.entities", 
    entityManagerFactoryRef = "entityManagerFactory", 
    transactionManagerRef = "transactionManager", 
    repositoryBaseClass = Dao.class)
@Import({ DataSourceConfig.class }) //Explained below
public class TestConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public List<String> modelJPA() {
        return Collections.singletonList("es.carm.sms.ortopedia.entities");
    }

    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }

    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactory.setPackagesToScan(modelJPA().toArray(new String[modelJPA().size()]));
        entityManagerFactory.setDataSource(this.dataSource);
        JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
        return entityManagerFactory;
    }
}

Then if you want to connect with your database:

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
        dataSource.setUrl("jdbc:oracle:thin:@ip:port:sid");
        dataSource.setUsername("name");
        dataSource.setPassword("pass");
        return dataSource;
    }

}

Now you have it all set up, you just need to create your test importing your configurations:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
public class TestCase {...}

You will get your spring context initialized with access to all your resources (MVC) Services, DAO and Model.

like image 24
UHDante Avatar answered Oct 12 '22 21:10

UHDante