проблема, с которой я сталкиваюсь, состоит из 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();
}
}