简体   繁体   English

使用Async Jobs和Request Scope Bean进行Spring Boot集成测试

[英]Spring Boot integration testing with Async Jobs and Request Scope beans

the problem I'm facing consists of 3 factors: 我面临的问题包括3个因素:

  • Spring Boot integration testing with Maven & JUnit 使用Maven和JUnit进行Spring Boot集成测试
  • A request scope bean within the context of the application 应用程序上下文中的请求范围Bean
  • 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) 在Spring Boot启动时 ,一个异步作业会同时从Data Source中获取数据并将其存储在缓存中(Guava CacheLoader)
  • When user requests this data, intercept the request, authenticate header tokens, store user info in Request scope bean and continue. 当用户请求此数据时,请拦截请求,对标头令牌进行身份验证,将用户信息存储在“ 请求范围” Bean中并继续。
  • Fetch data from Cache and return to user. 从缓存中获取数据并返回给用户。

The error I get when trying to run Maven Test: 我在尝试运行Maven测试时遇到的错误:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.requestBean': Scope 'request' is not active for the current thread; org.springframework.beans.factory.BeanCreationException:创建名称为“ scopedTarget.requestBean”的bean时出错:当前线程的作用域“ request”未激活; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; 如果您打算从单例中引用它,请考虑为此bean定义作用域代理。 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? 嵌套异常为java.lang.IllegalStateException:未找到线程绑定的请求:您是在实际Web请求之外引用请求属性,还是在原始接收线程之外处理请求? 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. 如果您实际上是在Web请求中操作并且仍然收到此消息,则您的代码可能在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter公开当前请求。

Important note & clue to solving this : When the controller fetches directly from DAO (Data Source), the tests pass!!! 解决此问题的重要说明和线索 :当控制器直接从DAO(数据源)获取时,测试通过!!! Only when it fetches from Guava Cache it fails. 仅当它从Guava缓存中获取时才会失败。

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());
  }
}

Interceptor code is straight forward: 拦截器代码很简单:

@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)
  }
}

How we instantiate request bean: 我们如何实例化请求bean:

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

And finally, the tests: 最后,测试:

@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();
  }
}

I couldn't find a proper fix, so I ended up delaying the async jobs a couple of seconds after server start: 我找不到适当的修复程序,因此我在服务器启动后延迟了几秒钟来延迟异步作业:

 @PostConstruct
  private void refreshCache() {
    /* Refresh cache concurrently */    
    Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
                () -> resellersCache.refresh(this.getClass().toString()), 33,
                cacheRefreshTimeInterval, TimeUnit.SECONDS);
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM