Как стать автором
Обновить

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

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

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

Вскоре я нагуглил в интернетах такой красивый вариант:


@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.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>


Для этого заведём в 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 статических методов его в инициализации юнит теста.

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

Буду рад, если кто то подскажет как лучше решать все эти проблемы.
Теги:
Хабы:
Всего голосов 5: ↑3 и ↓2+1
Комментарии9

Публикации

Истории

Работа

Java разработчик
350 вакансий

Ближайшие события