Search code examples
javaspringjunitspring-testdbunit

Alternative for @Autowired in Spring, which won't initialize the bean before test class setup


I have a Spring Bean class which communicates with the DB, and I'm working on writing a test case for it.

I'm making use of DbUnit which sets up an in-memory database (based on extending a base DbUnit class) using JUnit Annotation @Before.

The issue is with the @Autowired bean I'm trying to test: it accesses the database in its @Postconstruct method, and this happens before DB Setup, note the class itself is annotated with @Component.

So, I need a way or annotation to use so that the bean I'm testing gets initialized only after the @Before JUnit method runs.


Solution

  • This is common problem for spring and there is common solution to solve it. You need to build so-called "three phase construction".

    Generally you want to initialize your bean only after you set all your spring context (with database). So you need to listen when spring will be initialized and then execute your logic.

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface PostProxy {
    }
    

    This class should be defined as a bean within your context.

    public class PostProxySpringContextListener implements ApplicationListener<ContextRefreshedEvent> {
    
    private static Logger LOG = LoggerFactory.getLogger(PostProxySpringContextListener.class);
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            String originalClassName = getOriginalClassName(beanDefinitionName, event);
            if (originalClassName != null) {
                invokeAnnotatedMethods(context, beanDefinitionName, originalClassName);
            }
        }
    }
    
    private String getOriginalClassName(String name, ContextRefreshedEvent event) {
        try {
            ConfigurableListableBeanFactory factory =
                    (ConfigurableListableBeanFactory)event.getApplicationContext().getAutowireCapableBeanFactory();
            BeanDefinition beanDefinition = factory.getBeanDefinition(name);
            return beanDefinition.getBeanClassName();
        } catch (NoSuchBeanDefinitionException e) {
            LOG.debug("Can't get bean definition for : " + name);
            return null;
        }
    }
    
    private void invokeAnnotatedMethods(ApplicationContext context, String beanDefinitionName, String originalClassName) {
        try {
            Class<?> originalClass = Class.forName(originalClassName);
            Method[] methods = originalClass.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(PostProxy.class)) {
                    LOG.info("Executing @PostProxy annotated initialization method: " + method.toString());
                    Object bean = context.getBean(beanDefinitionName);
                    Method currentMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                    currentMethod.invoke(bean);
                }
            }
        } catch (ClassNotFoundException e) {
            LOG.trace("No class instance for bean " + beanDefinitionName + " with class name " + originalClassName);
        } catch (NoSuchMethodException e) {
            LOG.error("Error finding @PostProxy method for bean " + beanDefinitionName, e);
        } catch (InvocationTargetException e) {
            LOG.error("Error invoking @PostProxy method for bean " + beanDefinitionName, e);
        } catch (IllegalAccessException e) {
            LOG.error("Can't invoke annotated method in bean" + beanDefinitionName + " with class name " + originalClassName
                    + ". Please check access modifiers on @PostProxy methods.", e);
        }
    }
    }