the problem I'm facing consists of 3 factors:
- Spring Boot integration testing with Maven & JUnit
- A request scope bean within the context of the application
- An async job that runs on app start.
The problem occurs on testing only, the application runs as intended when building without tests.
Simplified application flow:
- On Spring Boot startup, an async job concurrently fetches data from Data Source and stores it on cache (Guava CacheLoader)
- When user requests this data, intercept the request, authenticate header tokens, store user info in Request scope bean and continue.
- Fetch data from Cache and return to user.
The error I get when trying to run Maven Test:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.requestBean': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Important note & clue to solving this: When the controller fetches directly from DAO (Data Source), the tests pass!!! Only when it fetches from Guava Cache it fails.
Simplified code:
/* 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());
}
}
Aucun commentaire:
Enregistrer un commentaire