Mocking RestTemplateBuilder and RestTemplate in Spring integration test
Asked Answered
R

3

13

I have a REST resource that gets a RestTemplateBuilder injected to build a RestTemplate:

public MyClass(final RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.build();
}

I would like to test that class. I need to mock the calls the RestTemplate makes to another service:

request = restTemplate.getForEntity(uri, String.class);

I tried this in my IT:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyIT {

@Autowired
private TestRestTemplate testRestTemplate;
@MockBean
private RestTemplateBuilder restTemplateBuilder;
@Mock
private RestTemplate restTemplate;

@Test
public void shouldntFail() throws IOException {

    ResponseEntity<String> responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);
    when(restTemplateBuilder.build()).thenReturn(restTemplate);
    when(restTemplate.getForEntity(any(URI.class), any(Class.class))).thenReturn(responseEntity);
...
ResponseEntity<String> response = testRestTemplate.postForEntity("/endpoint", request, String.class);
  ...
}
}

When I run the test, I get the following exception:

java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.test.web.client.TestRestTemplate': Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: RestTemplate must not be null

How do I do this correctly?

Royalroyalist answered 6/3, 2018 at 10:28 Comment(8)
Have u included the class that holds the bean TestRestTemplate? Spring is not able to find the bean "testRestTemplate" that u have autowired.Cauline
@RunWith(SpringRunner.class) @SpringBootTest(classes="Application.class") Where Application class will hold the @bean that u have autowiredCauline
@amRika I dont think that's it. I have another test method in the same class that uses TestRestTemplate and that method works fine if I comment out the other test and the MockBean and Mock annotated fields. I also have other projects where autowiring TestRestTemplate has worked.Royalroyalist
show your TestRestTemplate classMasuria
@Masuria it's the one already in spring: docs.spring.io/spring-boot/docs/current/api/org/springframework/…Royalroyalist
have you initialized your mocks?Masuria
@Masuria what do you mean by initialize? They're mocks.Royalroyalist
MockitoAnnotations.initMocks(this)Masuria
F
18

Your problem is the order of execution. The context is created containing your MockBean before you have a chance to set it up in your @Test. The solution is to provide a RestTemplateBuilder that's already fully setup when it's inserted into the context. You can do that like this.

Add the following to your @SpringBootTest annotation. Where TestApplication is your Spring Boot application class.

classes = {TestApplication.class, MyIT.ContextConfiguration.class},

Modify your class members thus, deleting your restTemplate and restTemplateBuilder.

@Autowired
private TestRestTemplate testRestTemplate;

Add a static inner class to your MyIT class:

@Configuration
static class ContextConfiguration {
  @Bean
  public RestTemplateBuilder restTemplateBuilder() {

    RestTemplateBuilder rtb = mock(RestTemplateBuilder.class);
    RestTemplate restTemplate = mock(RestTemplate.class);

    when(rtb.build()).thenReturn(restTemplate);
    return rtb;
  }
}

In your tests, modify the RestTemplate mock to do whatever you want:

@Test
public void someTest() {

  when(testRestTemplate.getRestTemplate().getForEntity(...
}
Fellows answered 7/3, 2018 at 14:38 Comment(4)
Yeah, what I didn't take into consideration was that Spring only creates on application context, so it was the same context for my ITs. I ended up wrapping the rest call into a service and mocking that service.Royalroyalist
It is worth noting that the ContextConfiguration will be visible for all test classes, not only the current one. This can be prohibited by using a @ConditionalOn... annotation for the @Bean.Ruttish
I am trying to use the same approach. However I still got "org.mockito.exceptions.misusing.InjectMocksException: Cannot instantiate InjectMocks". The only difference is that within the test I annotated my Service with InjectMocks and also have another Mock which I need to use in a test. Do you have any idea what I did wrong?Eyeful
one correction in the above solution, instead of Configuration use @TestConfiguration , it worked in my case, However I am having the similar problem but need to set the TestConfiguration annotation, then the test run successfullyFuselage
P
0

I had a similar issue. My Service class had RestTemplateBuilder in constructor and RestTemplate as private field and I wanted to mock RestTemplate.

I solved using ReflectionTestUtils Spring class to change my private field RestTemplate after my Service class Bean was created.

@Mock
private RestTemplate restTemplate;

@Autowired
private MyServiceClass myServiceClass;

@BeforeEach
void setUp() {
    ReflectionTestUtils.setField(myServiceClass, "restTemplate", restTemplate);
}
Pinelli answered 5/8, 2022 at 19:34 Comment(0)
S
-2

Just use the Reflection to substitute restTemplate created by builder for mocked restTemplate.

For example:

@Mock
private RestTemplate mockRestTemplate;

@Test
public void shouldntFail() throws IOException {
    this.initRestTemplate()

    ResponseEntity<String> responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);
    when(mockRestTemplate.getForEntity(any(URI.class), any(Class.class))).thenReturn(responseEntity);
}

private void initRestTemplate() {
    Field field = ReflectionUtils.findField(MyClass.class, "restTemplate");
    if (ObjectUtils.isNotEmpty(field)) {
      field.setAccessible(Boolean.TRUE);
      ReflectionUtils.setField(field, *yourObjectWithRestRemplate*, mockRestTemplate);
    }
}

P.S. I used ReflectionUtils provided by Spring, you can use any you prefer.
Sulphurbottom answered 22/7, 2021 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.