Mockito模拟方法返回null

时间:2019-09-20 19:21:43

标签: java spring spring-boot mocking mockito

我有一个标准的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();
  }

我很困惑,所有建议都值得赞赏! :|

1 个答案:

答案 0 :(得分:0)

要对此进行跟进,我的问题是因为我尝试测试的方法被Spring Boot的@Async注释标记,导致竞态条件和某些模拟未正确配置。