所以我有两个类:一个 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 中引用过)
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)”为空
您缺少
@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:
警告: 常规的干净代码很少需要此功能!将其留给遗留代码。模拟模拟以返回模拟,返回模拟,(...),返回一些有意义的暗示违反德米特法则或模拟值对象(众所周知的反模式)。
我有一天在网上看到的一句好话:每次模拟返回模拟时,就有一个仙女死去。