在我的Spring应用程序中,我有一个服务MyService
。MyService
调用一个外部API,对那里的产品进行计数并返回结果。为了调用那个API,它使用Spring模块RestTemplate
。为了注入RestTemplate
,在DependencyConfig
:中将其配置为@Bean
@Service
public class ServiceImpl implements MyService {
private final RestTemplate restTemplate;
public ServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public String serve() {
ShopProductsResponse result = restTemplate.getForObject(
"https://api.predic8.de/shop/products/",
ShopProductsResponse.class
);
if (result == null) {
return "Error occurred";
}
return String.format("Found %s products", result.getProducts().length);
}
}
现在我想测试它,而不需要调用外部API。因此,我通过@Autowired
注入MyService
,通过@MockBean
注入RestTemplate
。要定义restTemplate.getForObject(...)
的结果,我使用Mockito.when(...).thenReturn(...)
方法模拟结果:
@SpringBootTest
class ServiceTest {
@MockBean
private RestTemplate restTemplate;
@Autowired
private MyService service;
@BeforeEach
void init() {
final ShopProductsResponse response = new ShopProductsResponse();
response.setProducts(new Product[0]);
Mockito.when(restTemplate.getForObject(Mockito.any(), Mockito.any())).thenReturn(response);
}
@Test
void test() {
final String expectation = "Found 0 products";
String result = service.serve();
Mockito.verify(restTemplate, Mockito.times(1)).getForObject(
ArgumentMatchers.eq("https://api.predic8.de/shop/products/"),
ArgumentMatchers.eq(ShopProductsResponse.class)
);
Assertions.assertEquals(expectation, result);
}
}
问题是,restTemplate.getForObject(...)
的结果为空,因此测试失败,并显示消息
org.opentest4j.AssertionFailedError:
Expected :Found 0 products
Actual :Error occurred
所以我的问题是,我做错了什么?我以为我是在告诉那个骗子该回什么。我该如何纠正?
如果有人想尝试一下,我将示例项目推送到Github:https://github.com/roman-wedemeier/spring_example
试图在网上找到答案,我很困惑,Junit(4/5(有不同的版本。在某个地方,我发现了一个关于直接模拟服务的教程,这不是我想做的。另一方面,有人解释了如何模拟服务的依赖性,但不使用Spring的依赖性注入。
restTemplate.getForObject()
方法有多组参数,可以使用调用
restTemplate.getForObject(String url, Class<T> responseType, Object... uriVariables)
restTemplate.getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
restTemplate.getForObject(URI url, Class<T> responseType)
所以您可能通过提供最宽的匹配器(Mockito.any()
(来模拟另一个方法调用。我建议您尝试通过提供更具体的匹配器来模拟restTemplate.getForObject()
方法调用,例如:
Mockito.when(restTemplate.getForObject(Mockito.anyString(), Mockito.any())).thenReturn(response);
并且测试应该成功通过。
此外,您可以只使用Mockito和DI为服务进行单元测试,而不是设置整个Spring应用程序上下文(由@SpringBootTest
注释完成(。这里没有必要,只会使测试持续更长时间。以下是实现测试的另一种方法:
class ServiceTest {
private RestTemplate restTemplate;
private MyService service;
@BeforeEach
void init() {
final ShopProductsResponse response = new ShopProductsResponse();
response.setProducts(new Product[0]);
this.restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(this.restTemplate.getForObject(Mockito.anyString(), Mockito.any())).thenReturn(response);
this.service = new ServiceImpl(restTemplate);
}
@Test
void test() {
final String expectation = "Found 0 products";
String result = service.serve();
Mockito.verify(restTemplate, Mockito.times(1)).getForObject(
ArgumentMatchers.eq("https://api.predic8.de/shop/products/"),
ArgumentMatchers.eq(ShopProductsResponse.class)
);
Assertions.assertEquals(expectation, result);
}
}