在Spring集成测试中模拟RestTemplateBuilder和RestTemplate

13

我有一个REST资源,它注入了RestTemplateBuilder以构建RestTemplate

public MyClass(final RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.build();
}
我想测试那个类。我需要模拟RestTemplate对另一个服务的调用:
request = restTemplate.getForEntity(uri, String.class);

我在我的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);
  ...
}
}

当我运行测试时,出现以下异常:

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

我该如何正确地执行这个操作?


你是否已经包含了持有TestRestTemplate Bean的类?Spring 找不到你自动装配的 "testRestTemplate" Bean。 - Amit Kumar Lal
@RunWith(SpringRunner.class) @SpringBootTest(classes="Application.class") 其中,Application类将持有您已自动装配的@Bean。 - Amit Kumar Lal
你初始化了你的模拟对象吗? - pvpkiran
@pvpkiran,你说的“initialize”是什么意思?它们是模拟。 - user3629892
1
MockitoAnnotations.initMocks(this) - pvpkiran
显示剩余3条评论
3个回答

19

你的问题是执行顺序。在你有机会在@Test中对MockBean进行设置之前,上下文已经创建并包含了它。解决方法是提供一个完全设置好的RestTemplateBuilder,并在插入到上下文时使用它。你可以这样做。

将以下内容添加到你的 @SpringBootTest 注解中,其中 TestApplication 是你的 Spring Boot 应用程序类。

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

修改你的类成员,删除你的restTemplate和restTemplateBuilder。

@Autowired
private TestRestTemplate testRestTemplate;

在你的MyIT类中添加一个静态内部类:

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

在您的测试中,修改RestTemplate模拟对象以执行您想要的任何操作:

@Test
public void someTest() {

  when(testRestTemplate.getRestTemplate().getForEntity(...
}

是的,我没有考虑到的是Spring只创建一个应用上下文,所以我的集成测试(ITs)使用的是同一个上下文。最后,我将REST调用封装到一个服务中,并对该服务进行了模拟。 - user3629892
值得注意的是,ContextConfiguration 将对所有测试类可见,而不仅仅是当前的测试类。可以通过为 @Bean 使用 @ConditionalOn... 注释来禁止这种情况发生。 - Stephan Windmüller
我正在尝试使用相同的方法。但是我仍然得到了"org.mockito.exceptions.misusing.InjectMocksException: Cannot instantiate InjectMocks"。唯一的区别是,在测试中,我用InjectMocks注释了我的Service,并且还有另一个Mock,我需要在测试中使用它。你有任何想法我做错了什么吗? - Peters_
1
在上面的解决方案中有一个更正,应该使用 @TestConfiguration 而不是 Configuration,在我的情况下它起作用了。然而,我遇到了类似的问题,但需要设置 TestConfiguration 注释,然后测试才能成功运行。 - raj03

0

我遇到了类似的问题。我的Service类在构造函数中有RestTemplateBuilder,并且有一个私有字段RestTemplate,我想要模拟RestTemplate

我使用了Spring的ReflectionTestUtils类,在创建我的Service类Bean之后更改了我的私有字段RestTemplate,从而解决了这个问题。

@Mock
private RestTemplate restTemplate;

@Autowired
private MyServiceClass myServiceClass;

@BeforeEach
void setUp() {
    ReflectionTestUtils.setField(myServiceClass, "restTemplate", restTemplate);
}

-2

只需使用反射来替换由构建器创建的restTemplate以用于模拟restTemplate。

例如:

@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.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接