我有一个标准的StorageService,它存在于Web API的Spring Boot应用程序中。
@Component
@Service
@Slf4j
public class StorageService {
@Autowired
private AmazonS3 s3Client;
@Autowired
private RestTemplate restTemplate;
@Value("${app.aws.s3.bucket}")
private String bucket;
@Async
public boolean fetchAndUpload(List<URI> uris) {
List<CompletableFuture<PutObjectResult>> futures = uris.stream().map(uri ->
fetchAsync(uri).thenApplyAsync((asset) -> put(getName(uri.toString()), asset))
).collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
return true;
}
@Async
private CompletableFuture<InputStream> fetchAsync(URI uri) {
return CompletableFuture.supplyAsync(() -> {
InputStream resp;
try {
// asdf is null here when running my unit tests
Resource asdf = restTemplate.getForObject(uri, Resource.class);
resp = Objects.requireNonNull(asdf).getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
return resp;
});
}
private PutObjectResult put(String name, InputStream data) {
PutObjectRequest request = new PutObjectRequest(bucket, name, data, new ObjectMetadata());
return s3Client.putObject(request);
}
}
这是一个集成测试,至少可以成功获取集成测试给出的图像:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebClient
public class StorageServiceIT {
@Value("${app.aws.s3.access.key}")
private String accessKey;
@Value("${app.aws.s3.secret.key")
private String secretKey;
@Spy
private AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
.withRegion(Regions.US_EAST_1)
.build();
@Spy
private RestTemplate restTemplate = new RestTemplateBuilder().build();
@MockBean
private SignService signingService;
@Autowired
private StorageService storageService;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void fetchAsync() throws URISyntaxException {
List<URI> uris = List.of(
new URI("https://upload.wikimedia.org/wikipedia/commons/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg"),
new URI("https://upload.wikimedia.org/wikipedia/commons/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg")
);
storageService.fetchAndUpload(uris);
}
}
但是,以下单元测试无法成功模拟restTemplate.getForObject
调用,即使将两个参数都设置为any()
,它也始终返回null。
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebClient
public class StorageServiceTest {
@MockBean
private AmazonS3 s3Client;
@MockBean
private RestTemplate restTemplate;
@MockBean
private SignService signingService;
@Autowired
// @InjectMocks ???
private StorageService storageService;
@Value("${app.aws.s3.bucket}")
private String bucket;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
List<URI> randomUris(int num) {
final String base = "https://example.com/%s";
return Stream.iterate(0, i -> i)
.limit(num)
.map(o -> URI.create(String.format(base, UUID.randomUUID().toString())))
.collect(Collectors.toList());
}
@Test
public void fetchAsyncTest() {
List<URI> uris = randomUris(2);
uris.forEach(uri -> {
ByteArrayInputStream data = new ByteArrayInputStream(
Integer.toString(uri.hashCode()).getBytes());
PutObjectRequest request = new PutObjectRequest(
bucket, getName(uri.toString()), data, new ObjectMetadata());
Resource getResult = mock(Resource.class);
try {
doReturn(data).when(getResult).getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
doReturn(data).when(restTemplate).getForObject(any(), any());
// none are working
// doReturn(data).when(restTemplate).getForObject(eq(uri), eq(Resource.class));
// doReturn(data).when(restTemplate).getForObject(uri, Resource.class);
// when(restTemplate.getForObject(uri, Resource.class)).thenReturn(data);
// when(restTemplate.getForObject(eq(uri), eq(Resource.class))).thenReturn(data);
// when(restTemplate.getForObject(any(), any())).thenReturn(data);
PutObjectResult res = new PutObjectResult();
doReturn(res).when(s3Client).putObject(eq(request));
// not working here as well, i think
// doReturn(res).when(s3Client).putObject(request);
// doReturn(res).when(s3Client).putObject(any());
// when(s3Client.putObject(eq(request))).thenReturn(res);
// when(s3Client.putObject(request)).thenReturn(res);
// when(s3Client.putObject(any())).thenReturn(res);
});
boolean res = storageService.fetchAndUpload(uris);
}
}
以防万一,这就是我构建RestTemplate
的方式:
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
我很困惑,所有建议都值得赞赏! :|
答案 0 :(得分:0)
要对此进行跟进,我的问题是因为我尝试测试的方法被Spring Boot的@Async
注释标记,导致竞态条件和某些模拟未正确配置。