Интеграционное тестирование Spring Boot с Async Jobs и Request Scope Bean - PullRequest
0 голосов
/ 01 июля 2018

проблема, с которой я сталкиваюсь, состоит из 3 факторов:

  • Интеграционное тестирование Spring Boot с Maven & JUnit
  • Компонент области запроса в контексте приложения
  • Асинхронное задание, запускаемое при запуске приложения.

Проблема возникает только при тестировании , приложение запускается как задумано при сборке без тестов.

Упрощенный поток приложения:

  • При запуске Spring Boot , задание async одновременно извлекает данные из источника данных и сохраняет их в кеше (Guava CacheLoader)
  • Когда пользователь запрашивает эти данные, перехватывает запрос, аутентифицирует токены заголовка, сохраняет информацию о пользователе в Область запроса bean и продолжает.
  • Получать данные из кэша и возвращать пользователю.

Ошибка при попытке запустить Maven Test:

org.springframework.beans.factory.BeanCreationException: Ошибка создания бина с именем 'scopedTarget.requestBean': Scope 'request' не активен для текущего потока; рассмотрите возможность определения прокси-объекта с заданной областью действия для этого компонента, если вы собираетесь ссылаться на него из одноэлементного объекта; Вложенное исключение - java.lang.IllegalStateException: не найден привязанный к потоку запрос: Вы ссылаетесь на атрибуты запроса вне фактического веб-запроса или обрабатываете запрос вне первоначально получающего потока? Если вы действительно работаете с веб-запросом и по-прежнему получаете это сообщение, ваш код, вероятно, выполняется за пределами DispatcherServlet / DispatcherPortlet: в этом случае используйте RequestContextListener или RequestContextFilter для предоставления текущего запроса.

Важное примечание и ключ к решению этой проблемы : Когда контроллер выбирает непосредственно из DAO (Data Source), тесты проходят !!! Только при получении из Guava Cache происходит сбой.

Упрощенный код:

/* Part of the controller: */
 @Autowired AsyncCacheService cacheService;
 @RequestMapping("/resellers")
 public HashMap<String, Reseller> getAllResellers() throws Exception {
   return cacheService.getAllResellers();
   //When I switch to get directly from DAO below, tests pass.
   //return partnerDao.getAllResellers(); <-- get directly from DAO.
 }




/* The service which the controller calls */
@Service
public class AsyncCacheService {
  private LoadingCache<String, List<Reseller>> resellersCache;
  public AsyncCacheService() {

    resellersCache = CacheBuilder.newBuilder().build(new CacheLoader<String, 
    HashMap<String, Reseller>>() {
      public HashMap<String, Reseller> load(String key) throws Exception {
        return partnerDao.getAllResellers();
      }
    });
  }
  @PostConstruct
  private void refreshCache() {
    /* Refresh cache concurrently */    
    Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
                () -> resellersCache.refresh(this.getClass().toString()), 0, 
                cacheRefreshTimeInterval, TimeUnit.SECONDS);
  }
  /* Return resellers from cache */
  public HashMap<String, Reseller> getAllResellers() {
    return resellersCache.getUnchecked(this.getClass().toString());
  }
}

Код перехватчика прост:

@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
  private @Autowired RequestBean requestBean;
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse 
       response, Object handler) throws Exception {
    //Verify request and requestBean.set(email, ip, foo)
  }
}

Как мы создаем экземпляр объекта запроса:

@Bean
@RequestScope
public RequestBean requestBean() {
    return new RequestBean();
}

И, наконец, тесты:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
tester {
  @Test
  public void allResellersSizeTest() throws Exception {

    //MvcResult r = 

      mockMvc.perform(get("/api/resellers").header(authHeaderName, jwtToken))
            .andExpect(status().isOk());//.andReturn();
  }
}

1 Ответ

0 голосов
/ 04 июля 2018

Мне не удалось найти правильное решение, поэтому я отложил асинхронные задания через пару секунд после запуска сервера:

 @PostConstruct
  private void refreshCache() {
    /* Refresh cache concurrently */    
    Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
                () -> resellersCache.refresh(this.getClass().toString()), 33,
                cacheRefreshTimeInterval, TimeUnit.SECONDS);
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...