@EntityListeners Injection + jUnit Testing
Asked Answered
O

2

10

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;
    }
    ...
Osborn answered 1/12, 2017 at 10:3 Comment(6)
If it is null you aren't using the context. Your test makes me wonder if you are even using the context created by the test (I doubt it looking at what you are doing in your test).Ziegfeld
Thanks for your comment @M.Deinum, I am creating a context using @ContextConfiguration(classes = {ApplicationConfiguration.class}) in my BaseTest class. All injections and configurations work properly, apart from the Encryptor (called from the EntityListener)Osborn
As stated I doubt you are actually using that by the way you are obtaining a service...Ziegfeld
Thanks again! I see what you are saying, but, even if I pass the context to the service, how am I supposed to pass it to the Encryptor afterwards?Osborn
You shouldn't be passing around those things... I'm merely stating that your re doing things differently in your test then in your actual code. The fact that you have some kind of service factory to get a service for the test hints me at that. You should be injecting dependencies into your test case not use some other object to get them. However there is too little code here, with what is here it is a guessing game at best.Ziegfeld
I understand, but as I said, I am able to inject any service in my tests with no problem. I am injecting the factory instead for another requirement, which I don't believe plays any role on this. I will try to extend more my code , but as I see at the moment, it's more a theoretical question.Osborn
C
1

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.

Contrastive answered 23/12, 2017 at 9:10 Comment(0)
B
1

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.

Butterfish answered 21/12, 2017 at 20:7 Comment(1)
thanks for your answer. As I mentioned in the comments, injections work perfectly in my tests. My only problem is with the Injections inside the JPA EntityListener, when running tests.Osborn
C
1

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.

Contrastive answered 23/12, 2017 at 9:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.