dimanche 1 juillet 2018

Spring Boot integration testing with Async Jobs and Request Scope beans

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