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?
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(...
}
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);
}
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.