测试使用 RestClient bean 的类时无法模拟此 bean

问题描述 投票:0回答:1

所以我有两个类:一个 RestClient 类和另一个使用 RestClient bean 的 MetadataService 类。

这是 RestClient 类

@Configuration
public class RestClientConfig {
  //actual class is more complex than this as we are authenticating with kerberos and there's
  //code that generates kerberos token and injects it into the rest client builder 
  @Bean("customRestClient")
  public RestClient restClient(RestClient.Builder builder) {
    return builder.build();
  }
}
 

以下是服务等级

@Component
public class MetadataService {
  @Autowired 
  @Qualifier("customRestClient")
  RestClient customRestClient;
  
  public void makeCall() {
     try {
         final String response = customRestClient.method(HTTPMethod.POST).uri("http://example.com")
                                      .headers(getHeaders()).body(//some body here).retrieve()
                                      .toEntity(String.class);
         log.info(response);
     }
  }
}


现在我想为MetadataService编写一个测试类。

我尝试过的事情:

  1. 在restClient bean上使用@Mock注释,但这会导致异常(更多内容见下文)
  2. 在类上使用@RestClientTest,尽管我对如何使用它有很多困惑。

这是我迄今为止编写的测试类(我在 #1 中引用过)

public class MetadataServiceTest {

@Mock
RestClient customRestClient;

@InjectMock
MetadataService metadataService;

@Test
public void testMakeCall() {

when(customRestClient.method(HTTPMethod.POST).uri("http://example.com")
     .headers(any()).body(any()).retrieve()
     .toEntity(String.class)).thenReturn(response); 

metadataService.makeCall();
}

}

这会导致像这样的异常:

java.lang.NullPointerException:无法调用“org.springframework.web.client.RestClient$RequestBodyUriSpec(String, Object[])”,因为“org.springframework.web.client.RestClient.method(org. springframework.http.HttpMethod)”为空

java spring-boot unit-testing mockito spring-rest
1个回答
0
投票

您缺少

@ExtendWith(MockitoExtension.class)
注释来让您的测试在 Mockito 支持下运行。如果没有注释,
@Mock
和朋友们就完全被忽略了。

此外,您只会在

customRestClient
上存根第一级调用。如果没有现有的存根,Mockito 模拟对象将返回任何给定类型的默认值(数字类型为 0,布尔值为 false,空集合,对象和字符串为 null)。编写类似
when(mock.method()).thenReturn(result)
的内容仍然 must 调用
method
实例上的
mock
方法。没有办法解决这个问题,Mockito 无法改变 Java 语言的行为。

这意味着诸如

之类的行
when(customRestClient.method(HTTPMethod.POST).uri("http://example.com")
     .headers(any()).body(any()).retrieve()
     .toEntity(String.class)).thenReturn(response); 

将首先调用

customRestClient.method(HTTPMethod.POST)
,然后在其结果上调用
uri("http://example")
。由于
customRestClient.method
未存根,因此它返回默认值(即
null
)。您无法在空引用上调用方法,因此会遇到 NullPointerException。

您必须对每个级别的调用进行存根。第一个存根

customRestClient.method
返回一个模拟对象。然后对该模拟对象进行
uri
调用存根以返回另一个模拟对象。在该对象上存根
headers
等等。

幸运的是,Mockito 提供了一个有用的快捷方式:

@Mock(Answers.RETURNS_DEEP_STUBS)
。这将模拟对象设置为从其每个方法返回另一个模拟对象。随后,
customRestClient.method(HTTPMethod.POST)
调用中的
when
返回一个模拟,并在此模拟上调用
uri("http://example.com")
将返回另一个模拟,依此类推。最终,对从调用链返回的最终模拟对象的
toEntity
调用将被存根以返回
response

只需将其添加为注释上的属性即可:

@Mock(RETURNS_DEEP_STUBS)
RestClient customRestClient;

只需注意

RETURNS_DEEP_STUBS
的 JavaDoc:

警告: 常规的干净代码很少需要此功能!将其留给遗留代码。模拟模拟以返回模拟,返回模拟,(...),返回一些有意义的暗示违反德米特法则或模拟值对象(众所周知的反模式)。

我有一天在网上看到的一句好话:每次模拟返回模拟时,就有一个仙女死去。

© www.soinside.com 2019 - 2024. All rights reserved.