Spring boot Junit Test ResttemplateBuilder 构建方法给出空点异常

问题描述

我试图从下面的服务类中覆盖断路器回退方法

@Component
public class UserService {
  private static final String UNABLE_TO_CONNECT_TO_USER_SERVICE ="User service not available";
  
  @Value("${user.service.url}")
  private String baseUrl;
  
  @Value("${user.service.username}")
  private String userName;

  @Value("${user.service.password}")
  private String password;

   public UserService(
      RestTemplateBuilder restTemplateBuilder,@Qualifier("userService_client_factory")
          HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory) {
    var template = restTemplateBuilder.build();
    template.setRequestFactory(httpComponentsClientHttpRequestFactory);
    this.restTemplate = template;
  }

  @CircuitBreaker(name = "userservice",fallbackMethod = "fallback")
  @Loggable
  public User getUser(String userId) {
    try {
      LOGGER.debug("calling getUser for userId {}",userId);
      ResponseEntity<User> responseEntity =
          restTemplate.exchange(
              buildUrl("/v2/{user-id}/user",userId),HttpMethod.GET,new httpentity<>(buildHeaders()),new ParameterizedTypeReference<User>() {});

      return responseEntity.getBody();
    } catch (HttpServerErrorException | ResourceAccessException e) {
      throw new ServiceInvokeException(UNABLE_TO_CONNECT_TO_USER_SERVICE,e);
    } catch (RestClientException e) {
      throw new UserException(e.getMessage(),"Get User Failed");
    }
  }

  @SuppressWarnings("all")
  private User fallback(String userId,ServiceInvokeException e) {
    throw new ServiceInvokeException(
        format(UNABLE_TO_CONNECT_TO_USER_SERVICE,e.getMessage()),e);
  }

  private HttpHeaders buildHeaders() {
    var headers = new HttpHeaders();
    headers.setAccept(singletonList(APPLICATION_JSON));
    headers.setContentType(APPLICATION_JSON);
    headers.setBasicAuth(userName,password);
    return headers;
  }

  private URI buildUrl(String path,String userId) {
    Map<String,String> pathParameters = new HashMap<>();
    pathParameters.put("user-id",userId);
    String urlPath = baseUrl + path;
    return UriComponentsBuilder.fromUriString(urlPath)
        .buildAndExpand(pathParameters)
        .toUri();
  }
}

下面是编写的Junit测试用例

@RunWith(springrunner.class)
@DirtiesContext
@ContextConfiguration(
  classes = {CircuitBreakerAutoConfiguration.class,UserService.class})
public class UserServiceFallBackTest {

  private static final String USER_NAME = "userName";
  private static final String PASSWORD = "password";
  private static final String USERNAME_VAL = "uname";
  private static final String PWD_VAL = "pwd";
  private static final String userId = "test-userId";
  private static final String ERROR_MESSAGE = "error message";
  @Autowired
  UserService userService;

  @Rule
  public ExpectedException thrown = ExpectedException.none();

  @TestConfiguration
  static class TestConfig {

    @Bean
    RestTemplateBuilder restTemplateBuilder() {
      RestTemplateBuilder restTemplateBuilder = mock(RestTemplateBuilder.class);
      RestTemplate restTemplate = mock(RestTemplate.class);
      ResponseEntity responseEntity = mock(ResponseEntity.class);

      when(restTemplateBuilder.build()).thenReturn(restTemplate);
      when(restTemplate.exchange(
        any(URI.class),eq(HttpMethod.GET),any(httpentity.class),any(ParameterizedTypeReference.class)))
        .thenThrow(new HttpServerErrorException(HttpStatus.BAD_GATEWAY));
      return restTemplateBuilder;
    }
    @Bean(name = "userService_client_factory")
    HttpComponentsClientHttpRequestFactory factory(){
      HttpComponentsClientHttpRequestFactory factory = mock(HttpComponentsClientHttpRequestFactory.class);
      return factory;
    }

    @Bean
    UserService userService() {
      return new UserService(restTemplateBuilder(),factory());
    }
  }

  @Test
  public void createFallbackWrapsExceptionAsServiceInvokeException() {
    thrown.expect(ServiceInvokeException.class);
    userService.getUser(userId);
  }

  @Test
  public void createFallbackWrapsExceptionAsRestClientException() {
    thrown.expect(UserException.class);
    userService.getUser(userId);
  }
}

测试用例在 UserService.java 类 var template = restTemplateBuilder.build();

给出 NullPointerException

这里 restTemplateBuilder.build() 给出空的 restTemaplate 对象

解决方法

模拟不会进入@Bean 配置。 @Bean 方法生成 bean,仅此而已,代码超出范围并在此之后停止工作。

分离模拟

如果您希望在每次测试之前运行某些代码,也可以使用 @Before;如果希望某些代码在所有测试之前运行一次,则可以使用 @BeforeAll

public class UserServiceFallBackTest {

  private static final String USER_NAME = "userName";
  private static final String PASSWORD = "password";
  private static final String USERNAME_VAL = "uname";
  private static final String PWD_VAL = "pwd";
  private static final String userId = "test-userId";
  private static final String ERROR_MESSAGE = "error message";
  @Autowired
  UserService userService;

  @Rule
  public ExpectedException thrown = ExpectedException.none();

  @Mock
  private RestTemplateBuilder restTemplateBuilder;

  @Mock
  private RestTemplate restTemplate;

  @Mock
  private ResponseEntity responseEntity;

  @Before
  private void init() {
     when(restTemplateBuilder.build()).thenReturn(restTemplate);
      when(restTemplate.exchange(
        any(URI.class),eq(HttpMethod.GET),any(HttpEntity.class),any(ParameterizedTypeReference.class)))
        .thenThrow(new HttpServerErrorException(HttpStatus.BAD_GATEWAY));
      return restTemplateBuilder;
  }

  @TestConfiguration
  static class TestConfig {

   
    @Bean(name = "userService_client_factory")
    HttpComponentsClientHttpRequestFactory factory(){
      HttpComponentsClientHttpRequestFactory factory = mock(HttpComponentsClientHttpRequestFactory.class);
      return factory;
    }

    @Bean
    UserService userService() {
      return new UserService(restTemplateBuilder(),factory());
    }
  }

  @Test
  public void createFallbackWrapsExceptionAsServiceInvokeException() {
    thrown.expect(ServiceInvokeException.class);
    userService.getUser(userId);
  }