Josh C
Josh C

Reputation: 370

Spring-Boot RestClientTest not correctly auto-configuring MockRestServiceServer due to unbound RestTemplate

EDIT: This question is specifically pertaining to the @RestClientTest annotation introduced in spring-boot 1.4.0 which is intended to replace the factory method.

Problem:

According to the documentation the @RestClientTest should correctly configure a MockRestServiceServer to use when testing a REST client. However when running a test I am getting an IllegalStateException saying the MockServerRestTemplateCustomizer has not been bound to a RestTemplate.

Its worth noting that I'm using Gson for deserialization and not Jackson, hence the exclude.

Does anyone know how to correctly use this new annotation? I haven't found any examples that require more configuration then when I have already.

Configuration:

@SpringBootConfiguration
@ComponentScan
@EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class})
public class ClientConfiguration {

...

   @Bean
   public RestTemplateBuilder restTemplateBuilder() {
       return new RestTemplateBuilder()
            .rootUri(rootUri)
            .basicAuthorization(username, password);
   }
}

Client:

@Service
public class ComponentsClientImpl implements ComponentsClient {

    private RestTemplate restTemplate;

    @Autowired
    public ComponentsClientImpl(RestTemplateBuilder builder) {
        this.restTemplate = builder.build();
    }

    public ResponseDTO getComponentDetails(RequestDTO requestDTO) {
        HttpEntity<RequestDTO> entity = new HttpEntity<>(requestDTO);
        ResponseEntity<ResponseDTO> response = 
              restTemplate.postForEntity("/api", entity, ResponseDTO.class);
        return response.getBody();
    }
}

Test

@RunWith(SpringRunner.class)
@RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {

    @Autowired
    private ComponentsClient client;

    @Autowired
    private MockRestServiceServer server;

    @Test
    public void getComponentDetailsWhenResultIsSuccessShouldReturnComponentDetails() throws Exception {
        server.expect(requestTo("/api"))
            .andRespond(withSuccess(getResponseJson(), APPLICATION_JSON));

        ResponseDTO response = client.getComponentDetails(requestDto);
        ResponseDTO expected = responseFromJson(getResponseJson());

        assertThat(response, is(expectedResponse));
    }
}

And the Exception:

java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate

Answer:

As per the answer below there is no need to declare a RestTemplateBuilder bean into the context as it is already provided by the spring-boot auto-configuration.

If the project is a spring-boot application (it has @SpringBootApplication annotation) this will work as intended. In the above case however the project was a client-library and thus had no main application.

In order to ensure the RestTemplateBuilder was injected correctly in the main application context (the bean having been removed) the component scan needs a CUSTOM filter (the one used by @SpringBootApplication)

@ComponentScan(excludeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})

Upvotes: 18

Views: 21713

Answers (2)

abaghel
abaghel

Reputation: 15297

You have RestTemplateBuilder at two places. At ClientConfiguration class and at ComponentsClientImpl class. Spring boot 1.4.0 auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. Remove below code from ClientConfiguration class and run your test.

 @Bean
 public RestTemplateBuilder restTemplateBuilder() {
   return new RestTemplateBuilder()
        .rootUri(rootUri)
        .basicAuthorization(username, password);
  }

Upvotes: 6

Deroude
Deroude

Reputation: 1112

The MockRestServiceServer instance should be constructed from the static factory, using a RestTemplate. See this article for a detailed description of the testing process.

In your example, you can do:

@RunWith(SpringRunner.class)
@RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {

    @Autowired
    private ComponentsClient client;

    @Autowired
    private RestTemplate template;

    private MockRestServiceServer server;

    @Before
    public void setUp() {
        server= MockRestServiceServer.createServer(restTemplate);
    }

    /*Do your test*/
}

Upvotes: 12

Related Questions