📜 ⬆️ ⬇️

Creating spring beans from regular classes and unit tests

We have both rich client and server actively using Spring. And a problem arose very quickly - how to use spring beans from regular classes (which themselves are not bins).

First, there were two ideas - pass them the necessary beans as arguments in the constructor or use some static field for storing the Spring context.
The first idea was deemed flawed. It turns out that the ski services must be pulled through a long series of designers.
The second idea was also considered vicious - the question arises who will initialize this field and when and what will happen with the unit tests.

Soon I put such a beautiful variant on the Internet:

')
@Service public class StaticContextHolder implements BeanFactoryAware { public static BeanFactory CONTEXT; public StaticContextHolder() { } public static Object getBean(String s) throws BeansException { return CONTEXT.getBean(s); } public static <T> T getBean(String s, Class<T> tClass) throws BeansException { return CONTEXT.getBean(s, tClass); } public static <T> T getBean(Class<T> tClass) throws BeansException { return CONTEXT.getBean(tClass); } public static Object getBean(String s, Object... objects) throws BeansException { return CONTEXT.getBean(s, objects); } public static boolean containsBean(String s) { return CONTEXT.containsBean(s); } @Override public void setBeanFactory(BeanFactory applicationContext) throws BeansException { logger.assertNull(CONTEXT, "CONTEXT is not null. Double Spring context creation?"); CONTEXT = applicationContext; } } 


And it works fine.
However, for unit tests, I had to modify it all a bit.

We have tests that create a spring context. Therefore, I added the following method to this class:

  @PreDestroy public void resetStatics() { CONTEXT=null; } 


Secondly, if the unit test does not create a spring text, but the class under test uses a StaticContextHolder, it is necessary that it get dependencies from it.

I made my fictitious context:
 public class FakeBeanFactory implements BeanFactory { private Map<String, Object> beans; public FakeBeanFactory (Map<String, Object> beans) { this.beans = beans; } @Override public Object getBean(String s) throws BeansException { return beans.get(s); } @Override public <T> T getBean(String s, Class<T> tClass) throws BeansException { return (T) beans.get(s); } @Override public <T> T getBean(Class<T> tClass) throws BeansException { return (T) beans.get(tClass.getName()); } @Override public Object getBean(String s, Object... objects) throws BeansException { return beans.get(s); } @Override public boolean containsBean(String s) { return false; //     } @Override public boolean isSingleton(String s) throws NoSuchBeanDefinitionException { return false; //     } @Override public boolean isPrototype(String s) throws NoSuchBeanDefinitionException { return false; //     } // .... } 


Now the unit test initialization looks like this:
  @Before public void init() { Map<String,Object> beans = new Map<String,Object>(); beans.put("service-dependency", new MockupDependencyImpl()); StaticContextHolder.CONTEXT = new FakeBeanFactory(beans)); } 


Now there is one more problem: prototype beans that are created through the init method, for example
  <bean id="PlanDefinitionReader" class="com.example.PlanDefinitionReader" scope="prototype" factory-method="createPlanDefinitionReader"> <constructor-arg index="0" value="null"/> <constructor-arg index="1" value="null"/> <constructor-arg index="2" value="null"/> </bean> 


To do this, let's get one more Map in FakeBeanfactory:
 Map<String s, Method m> initMethods 


and rewrite one method:
  public Object getBean(String s, Object... objects) throws BeansException { return initMethods.get(s).invoke(null, objects); } 


and initialize this Map of static methods in its initialization unit test.

Here you go. Something like this.

I would be glad if someone tells you how best to solve all these problems.

Source: https://habr.com/ru/post/139093/


All Articles