JAVA / Создание spring beans из обычных классов и юнит тесты

в 9:45, , рубрики: java, spring, unit test, метки: , , ,

У нас и rich client, и сервер активно используют Spring. И очень быстро возникла проблема — как использовать спринг бины из обычных классов (которые сами — не бины).

Сначала возникли две идеи — передавать им нужные бины как аргументы в конструкторе или использовать какое то статическое поле для хранения Spring context.
Первая идея была признана порочной. Получается, что ныжные сервисы надо тянуть через длинную череду конструкторов.
Вторая идея тоже была признана порочной — возникает вопрос кто и когда будет инициализировать это поле и что будет происходить с юнит тестами.

Вскоре я нагуглил в интернетах такой красивый вариант:
<a rel="nofollow" name="habracut">

@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;     }  }  

И это работает отлично.
Однако же для юнит тестов пришлось это всё немного модифицировать.

У нас есть тесты, которые создают спринг контекст. Поэтому я добавил в этот класс такое метод:

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

Во вторых, если юнит тест не создаёт спринг котекст, но тестируемый класс использует StaticContextHolder, надо чтобы зависимости он получал из него.

Я сделал свой фиктивный контекст:

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; // мне он не нужен     }      // ....  }      

Теперь инициализация юнит теста выглядит так:

  @Before   public void init() {        Map<String,Object> beans = new Map<String,Object>();        beans.put("service-dependency", new MockupDependencyImpl());        StaticContextHolder.CONTEXT = new FakeBeanFactory(beans));   } 

Теперь остаётся еще одна проблема: prototype beans, которые создаются через init method, например

 <bean id="PlanDefinitionReader" class="com.cadence.mdv.vplanDef.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> 

Для этого заведём в FakeBeanfactory еще один Map:

Map<String s, Method m> initMethods

и перепишем один метод:

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

и инициализируем этот Map статических методов его в инициализации юнит теста.

Ну вот. Как то так.

Буду рад, если кто то подскажет как лучше решать все эти проблемы.

Автор: javax


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js