I am new to Junit 5 . There are two functions in the class under test , The first function calls the second function and second function returns a value which is used in the first function for processing .
So I have created a mock for this class but not able to mock the second function call When I am testing the first function .
First function --exportOpportunityListing()
Second function -- entityToCsvReport()
public class OpportunityReportServiceImpl extends BaseService implements OpportunityReportService {
#Value("${nfs.mountPath}")
private String fileMountPath;
#Value("${take1.url.host}")
private String take1HostURL;
#Autowired
UsersRepository usersRepository;
#Autowired
MailUtil mailUtil;
#Autowired
OpportunityJDBCRepository ojdbc;
#Override
#Async
public void exportOpportunityListing(Map<String, Object> paramMap, List<OpportunityCriteria> lfvo,
String xRemoteUser) {
try {
List<OpportunityJDBCDTO> lo = ojdbc.getOppListWithoutPagination(paramMap, lfvo);
List<OpportunityReport> exportData = lo.parallelStream().map(this::entityToCsvReport)
.collect(Collectors.toList());
CsvCustomMappingStrategy<OpportunityReport> mappingStrategy = new CsvCustomMappingStrategy<>();
mappingStrategy.setType(OpportunityReport.class);
String dirPath = fileMountPath + REPORT_PATH;
File fileDir = new File(dirPath);
if (!fileDir.exists()) {
FileUtils.forceMkdir(fileDir);
}
String pathWithoutExtension = dirPath + "opportunity_data_"
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern(YYYYMMDDHHMMSS));
File reportFile = new File(pathWithoutExtension + EXTENSION_CSV);
Writer writer = new PrintWriter(reportFile);
StatefulBeanToCsv<OpportunityReport> beanToCsv = new StatefulBeanToCsvBuilder<OpportunityReport>(writer)
.withMappingStrategy(mappingStrategy).build();
beanToCsv.write(exportData);
writer.close();
String zipFilePath = pathWithoutExtension + EXTENSION_ZIP;
ZipUtil.zip(reportFile, zipFilePath);
Users remoteUser = usersRepository.findByUsername(xRemoteUser)
.orElseThrow(() -> new Take1Exception(ErrorMessage.USER_NOT_FOUND_WITH_USERNAME, xRemoteUser));
Mail mail = Mail.builder().to(new String[] { remoteUser.getEmail() })
.model(MailModel.builder().name(remoteUser.getName())
.body("Please find attached the opportunity report you requested.").build())
.subject("Opportunity Report").attachments(Arrays.asList(new File(zipFilePath))).build();
mailUtil.sendMail(mail);
Files.delete(reportFile.toPath());
} catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
throw new Take1Exception(ErrorMessage.INTERNAL_SERVER_EXCEPTION, e);
}
}
public OpportunityReport entityToCsvReport(OpportunityJDBCDTO o) {
OpportunityReport or = modelMapper.map(o, OpportunityReport.class);
or.setCurrency("USD");
or.setOnline(Boolean.TRUE.equals(o.getIsOnline()) ? "YES" : "NO");
return or;
}
}
Here is my JUnit Test case .
class OpportunityReportServiceImplTest {
#InjectMocks
OpportunityReportServiceImpl opportunityReportServiceImpl;
#Autowired
OpportunityReportServiceImpl ors;
#Mock
OpportunityJDBCRepository ojdbc;
#Mock
UsersRepository usersRepository;
#Mock
MailUtil mailUtil;
#Mock
ModelMapper mp;
String username = "anandabhishe";
String nfusername = "ananda";
Mail mail;
List<OpportunityJDBCDTO> lo = new ArrayList<OpportunityJDBCDTO>();
List<OpportunityReport> lor = new ArrayList<OpportunityReport>();
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(opportunityReportServiceImpl, "fileMountPath", ".");
ReflectionTestUtils.setField(opportunityReportServiceImpl, "take1HostURL", "");
lo.add(new OpportunityJDBCDTO());
lor.add(new OpportunityReport());
}
#Test
void testExportOpportunityListing() throws IOException {
OpportunityReport or = new OpportunityReport();
or.setCurrency("USD");
or.setOnline("Yes");
when(ojdbc.getOppListWithoutPagination(getParamMap(), getOppCriteria())).thenReturn(lo);
when(usersRepository.findByUsername(username)).thenReturn(Optional.of(getUser()));
doNothing().when(mailUtil).sendMail(mail);
// doNothing().when(opportunityReportServiceImpl).entityToCsvReport(oj);
when(opportunityReportServiceImpl.entityToCsvReport(getOpportunityJDBCDTO())).thenReturn(or);
opportunityReportServiceImpl.exportOpportunityListing(getParamMap(), getOppCriteria(), username);
assertTrue(true);
FileUtils.forceDelete(new File("." + REPORT_PATH));
}
private Map<String, Object> getParamMap() {
return new HashMap<String, Object>();
}
private List<OpportunityCriteria> getOppCriteria() {
List<OpportunityCriteria> loc = new ArrayList<>();
loc.add(new OpportunityCriteria());
return loc;
}
private OpportunityJDBCDTO getOpportunityJDBCDTO() {
OpportunityJDBCDTO oj = new OpportunityJDBCDTO();
oj.setIsOnline(true);
oj.setApplicationCount(2);
oj.setCost(200);
oj.setCountryCode("in");
oj.setCreationDate(LocalDateTime.now());
oj.setEndDate(LocalDate.now());
oj.setLocation("test");
oj.setOpportunityId(123);
oj.setOpportunityStatus("test");
oj.setOpportunityStatusId(1);
oj.setOpportunityTitle("test");
oj.setOpportunityType("test");
oj.setOpportunityTypeColor("test");
oj.setOpportunityTypeId(1);
oj.setPublishedAt(LocalDateTime.now());
oj.setPublishedBy("test");
oj.setPublishedByUserName("test");
oj.setRegistrationUrl("test");
oj.setStartDate(LocalDate.now());
oj.setSummary("test");
oj.setUserEmail("test");
oj.setUserFullName("test");
oj.setUserId(1);
oj.setUserName("test");
oj.setVendorName("test");
return oj;
}
private Users getUser() {
Users user = new Users();
return user;
}
}
I am getting Null Pointer Exception when the line in Test class is called :
when(opportunityReportServiceImpl.entityToCsvReport(getOpportunityJDBCDTO())).thenReturn(or);
I was missing mocking the modelmapper stub which is being used in second function , after I added that , the test passed .
OpportunityReport or = new OpportunityReport();
OpportunityJDBCDTO oj = new OpportunityJDBCDTO();
when(ojdbc.getOppListWithoutPagination(any(HashMap.class), anyList())).thenReturn(lo);
when(usersRepository.findByUsername(anyString())).thenReturn(Optional.of(getUser()));
doNothing().when(mailUtil).sendMail(mail);
doReturn(or).when(mp).map(oj, OpportunityReport.class);
opportunityReportServiceImpl.exportOpportunityListing(getParamMap(), getOppCriteria(), username);
assertTrue(true);
That's happening because opportunityReportServiceImpl is not a mock - it's the object that you're trying to test, but you're trying to stub a method of it as if it were a mock.
I would recommend that you don't try to stub the methods of the object that you're trying to test. But if you have to, you'll need to declare it as a #Spy. Then to stub it, you'll need the doReturn/when syntax instead of when/thenReturn. This might look like
doReturn(lo).when(ojdbc).getOppListWithoutPagination(getParamMap(), getOppCriteria());
Related
We are integrating our application with JIRA, to create an issue with attachment.
Method 1:
public JSONObject createIssue() {
IssueRestClient issueClient = restClient.getIssueClient();
MetadataRestClient metadataClient = restClient.getMetadataClient();
Map<String, Long> priorityMap = new HashMap<>();
Map<String, Long> issueMap = new HashMap<>();
Map<String, String> fieldsMap = new HashMap<>();
// To get the list of all types of priorities
metadataClient.getPriorities().claim().forEach(priorityType -> priorityMap.put(priorityType.getName(), priorityType.getId()));
// To get the list of all issue types
metadataClient.getIssueTypes().claim().forEach(issueType -> issueMap.put(issueType.getName(), issueType.getId()));
// To get the list of all fields, which includes the custom fields
metadataClient.getFields().claim().forEach(fieldType -> fieldsMap.put(fieldType.getName(), fieldType.getId()));
// skipped the remaining code
}
Method 2:
public void addAttachmentToIssue(String issueId, InputStream inputStream, String fileNameWithExtension) {
Issue issue = null;
IssueRestClient issueClient = restClient.getIssueClient();
Promise<Issue> promiseIssue = issueClient.getIssue(issueId);
issue = promiseIssue.claim();
issueClient.addAttachment(issue.getAttachmentsUri(), inputStream, fileNameWithExtension);
}
I'm writing unit tests using Mockito.
public class JIRATest{
#InjectMocks
JIRAService mockJiraService;
JiraRestClient mockJiraRestClient;
IssueRestClient mockIssueRestClient;
Promise mockPromise;
Issue mockIssue;
#BeforeEach
public void setup() {
mockJiraService = new JIRAService();
mockJiraRestClient = mock(JiraRestClient.class);
mockIssueRestClient = mock(IssueRestClient.class);
mockJiraService.setRestClient(mockJiraRestClient);
mockPromise = mock(Promise.class);
mockIssue = mock(Issue.class);
}
#Test
public void addAttachmentTest(){
Mockito.when(mockJiraRestClient.getIssueClient()).thenReturn(mockIssueRestClient);
Mockito.when(mockIssueRestClient.getIssue("DCU")).thenReturn(mockPromise);
Mockito.when(mockPromise.claim()).thenReturn(mockIssue);
Mockito.when(mockIssueRestClient.addAttachment(any(), any(), any()));
String webSite = "https://www.google.com";
URL url = new URL(webSite);
InputStream stream = url.openStream();
mockJiraService.addAttachmentToIssue("issueId", stream, "lowes-webpage.html");
}
}
How to mock these below objects, so that i wanted to use it in thenReturn() of Mockito?
1. Promise<Issue>
2. metadataClient.getPriorities() return Promise<Iterable<Priority>>
3. metadataClient.getPriorities().claim() return Iterable<Priority>
4. metadataClient.getIssueTypes() return Promise<Iterable<IssueType>>
5. metadataClient.getPriorities().claim() return Iterable<IssueType>
6. metadataClient.getFields() return Promise<Iterable<Field>>
7. metadataClient.getFields().claim() return Iterable<Field>
Trying to test repository method, but my test fails with following "Wanted but not invoked: cellphonesDao.deleteAllCellphones();"
Here is repo method:
#Override
public Single<Cellphone[]> getCellphones() {
Single<CellPhoneEntity[]> remoteCellphones =
networkModule.productApi()
.getCellPhones()
.onErrorResumeNext(cellphonesDao.getAllCellphones()); // todo return value if true
Single<CellPhoneEntity[]> localCellphones = cellphonesDao.getAllCellphones();
return Single.zip(remoteCellphones, localCellphones, (remote, local) -> {
if (!Arrays.equals(remote, local)) {
cellphonesDao.deleteAllCellphones();
for (CellPhoneEntity cellPhoneEntity : remote) {
cellphonesDao.insertCellphone(cellPhoneEntity);
}
}
return mapper.toCellphones(remote);
});
}
Main porpuse is to test repo method in correct way. Guess the way I chose is not good.
Here is test implementation:
class CellPhoneRepositoryImplTest {
NetworkModule networkModule;
CellphonesDao cellphonesDao;
CellphoneMapper cellphoneMapper;
CellPhoneRepositoryImpl cellPhoneRepository;
ProductAPI productAPI;
#BeforeEach
void setUp() {
networkModule = Mockito.mock(NetworkModule.class);
cellphonesDao = Mockito.mock(CellphonesDao.class);
productAPI = Mockito.mock(ProductAPI.class);
cellphoneMapper = new CellphoneMapper();
cellPhoneRepository = Mockito.spy(new CellPhoneRepositoryImpl(
networkModule,
cellphonesDao,
cellphoneMapper
));
}
#Test
void whenRemoteDataAreDifferentFromLocalDbIsUpdated() {
int numberOfCellphones = 5;
CellPhoneEntity[] remoteCellphones = DummyCellphoneEntityFactory.generateCellphones(numberOfCellphones);
CellPhoneEntity[] localCellphones = DummyCellphoneEntityFactory.generateCellphones(numberOfCellphones);
Mockito.when(networkModule.productApi()).thenReturn(productAPI);
Mockito.when(networkModule.productApi().getCellPhones()).thenReturn(wrapWithSingle(remoteCellphones));
// Mockito.when(networkModule.productApi().getCellPhones().onErrorResumeNext(cellphonesDao.getAllCellphones())).thenReturn(wrapWithSingle(remoteCellphones));
Mockito.when(cellphonesDao.getAllCellphones()).thenReturn(wrapWithSingle(localCellphones));
Mockito.doNothing().when(cellphonesDao).deleteAllCellphones();
cellPhoneRepository.getCellphones();
Mockito.verify(cellphonesDao, Mockito.times(1))
.deleteAllCellphones();
}
private Single<CellPhoneEntity[]> wrapWithSingle(CellPhoneEntity[] cellphones) {
return Single.just(cellphones);
}
}
I will be glad for any suggestion)
The code inside the returned Single isn't executed immediately, but your verifications are. Try calling cellPhoneRepository.getCellphones().blockingGet() instead of just cellPhoneRepository.getCellphones(). The blockingGet() should make your test wait until the Single is done executing.
I have following method:
#Component
public class WriteCsvToResponse {
private static final Logger LOGGER = LoggerFactory.getLogger(WriteCsvToResponse.class);
public void writeStatus(PrintWriter writer, Status status) {
try {
ColumnPositionMappingStrategy mapStrategy
= new ColumnPositionMappingStrategy();
mapStrategy.setType(Status.class);
String[] columns = new String[]{"id", "storeId", "status"};
mapStrategy.setColumnMapping(columns);
StatefulBeanToCsv btcsv = new StatefulBeanToCsvBuilder(writer)
.withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
.withMappingStrategy(mapStrategy)
.withSeparator(',')
.build();
btcsv.write(status);
} catch (CsvException ex) {
LOGGER.error("Error mapping Bean to CSV", ex);
}
}
I have no idea how to test it properly using mockito.
Use it to wrap the object status into csv format.
I used StringWriter to wrap the response in it.
There are no more details left, but it seems I have to create some words to pass the validation :)
You do not need mockito to test this method, only a java.io.StringWriter.
Here is how you can write the test for a nominal use:
#Test
void status_written_in_csv_format() {
// Setup
WriteCsvToResponse objectUnderTest = new WriteCsvToResponse ();
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
// Given
Status status = ...
// When
objectUnderTest.writeStatus(printWriter, status);
// Then
String actualCsv = stringWriter.toString();
assertThat(actualCsv.split("\n"))
.as("Produced CSV")
.containsExactly(
"id,storeId,status",
"42,142,OK");
}
This example assume some things about your Status object, but you have the general idea.
For assertions, I use AssertJ, but you can do the same with JUnit5 built-in assertions.
Hope this helps !
With a bit of a refactoring, where the Builder is a Spring Bean injected into this component.
You can then mock that builder to return a mocked StatefulBeanToCsv, specifically the write method, where you write the conditions and assertions. If you encounter an error, you throw some unchecked exception, like IllegalStateException, if everything is alright, you don't throw anything
you can write a test like this and change your input in write method:
#Test
public void test() {
WriteCsvToResponse writeCsvToResponse = mock(WriteCsvToResponse.class);
doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
write((Status)args[1]);
return null;
}
}).when(writeCsvToResponse).writeStatus(any(PrintWriter.class),any(Status.class));
writeCsvToResponse.writeStatus(writer, status);
}
public void write(Status status) {
// do anythings you like with status
}
I am using the akka framework with its Java API and mockito + Testkit for unit testing the actor
Here is the actor
public class K8sDeploymentCreator extends AbstractActor {
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
#Override
public Receive createReceive() {
return receiveBuilder().match(createK8sDeployment.class, msg -> {
KubeNamespace kubenamespace = new KubeNamespace();
KubeDeployment kubeDeployment = new KubeDeployment();
Namespace namespace = kubenamespace.createNamespace(msg.kubeClient, msg.service);
Deployment deployment = kubeDeployment.createDeployment(msg.service, msg.kubeClient, namespace);
log.info("sending complete depl msg");
getSender().tell(new K8sDeploymentComplete(deployment), getSelf());
})
.matchAny(o -> log.info("received unknown message")).build();
}
}
And here is the test class
public class K8sDeploymentCreatorTest extends JUnitSuite {
static ActorSystem system;
#Before
public void setup() {
system = ActorSystem.create();
KubeDeployment mockKubeDeployment = mock(KubeDeployment.class);
KubeNamespace mockKubeNamespace = mock(KubeNamespace.class);
Deployment deployment = Mockito.mock(Deployment.class);
Namespace namespace = Mockito.mock(Namespace.class);
KubernetesClient kubeClient = Mockito.mock(KubernetesClient.class);
Service serviceTodeploy = new Service("group","artifact","version");
DeployEnvironment deployEnvironment = new DeployEnvironment();
deployEnvironment.setName("K8sDeploymentCreatorTest");
serviceTodeploy.setDeployEnvironment(deployEnvironment);
when(mockKubeNamespace.createNamespace(kubeClient, serviceTodeploy)).thenReturn(namespace);
when(mockKubeDeployment.createDeployment(serviceTodeploy, kubeClient, namespace)).thenReturn(deployment);
}
#AfterClass
public static void teardown() {
TestKit.shutdownActorSystem(system);
system = null;
}
#Test
public void testK8sDeployment() {
new TestKit(system) {
{
final Props props = Props.create(K8sDeploymentCreator.class);
final ActorRef underTest = system.actorOf(props);
KubeDeployment mockKubeDeployment = mock(KubeDeployment.class);
KubeNamespace mockKubeNamespace = mock(KubeNamespace.class);
Deployment deployment = Mockito.mock(Deployment.class);
Namespace namespace = Mockito.mock(Namespace.class);
KubernetesClient kubeClient = Mockito.mock(KubernetesClient.class);
DeployEnvironment deployEnvironment = new DeployEnvironment();
deployEnvironment.setName("K8sDeploymentCreatorTest");
Service serviceTodeploy = new Service("group","artifact","version");
serviceTodeploy.setDeployEnvironment(deployEnvironment);
createK8sDeployment msg = new createK8sDeployment(serviceTodeploy, kubeClient);
underTest.tell(msg, getRef());
expectMsg(K8sDeploymentComplete)
}
};
}
}
This fails with a NPE (NullPointerException) trying to execute code inside createNamespace(). This method has been mocked, should it skip the excution and just return whatever the when statement says it should return?
Is this because I am instantiating a new objec of KubeNamspace and also KubeDeployment where as the contact is for mocks?
You are not actually mocking anything in your test. You are creating mock objects but they are not getting injected into the code under test. Your actor is executing the following code on response to a message:
KubeNamespace kubeNamespace = new KubeNamespace();
KubeDeployment kubeDeployment = new KubeDeployment();
This creates new un-mocked objects which will run their course as coded -- and often result in NPEs since they don't have the external dependencies they rely upon.
If you want to mock objects that are created this way you either have to refactor your code to extract the creation of them into a mockable factory class or use a more invasive mock library such as PowerMock or jMockit.
Example of Factory mock
class KubeFactory {
public KubeNamespace makeNamespace() {
return new KubeNamespace();
}
public KubeDeployment makeDeployment() {
return new KubeDeployment();
}
}
public class K8sDeploymentCreator extends AbstractActor {
private final KubeFactory factory;
K8sDeploymentCreator() {
this(new KubeFactory());
}
// This constructor allows you to override the factory used for testing
K8sDeploymentCreator(KubeFactory factory) {
this.factory = factory;
}
#Override
public Receive createReceive() {
return receiveBuilder().match(createK8sDeployment.class, msg -> {
KubeNamespace kubenamespace = factory.makeNamespace();
KubeDeployment kubeDeployment = factory.makeDeployment();
// rest is as before...
});
}
}
Then in your test class you create a test KubeFactory which returns mocked instances for the classes you are testing with:
#Test
public void testK8sDeployment() {
new TestKit(system) {
{
final KubeFactory mockFactory = mock(KubeFactory.class);
when(mockFactory.makeNamespace()).thenReturn(mockKubeNamespace);
when(mockFactory.makeDeployment()).thenReturn(mockKubeDeployment);
final Props props = Props.create(K8sDeploymentCreator.class, mockFactory);
final ActorRef underTest = system.actorOf(props);
// and so on...
}
}
}
I am writing a controller, that I need to make it asynchronous. How can I deal with a list of ListenableFuture? Because I have a list of URLs that I need to send GET request one by one, what is the best solution for it?
#RequestMapping(value = "/repositories", method = RequestMethod.GET)
private void getUsername(#RequestParam(value = "username") String username) {
System.out.println(username);
List<ListenableFuture> futureList = githubRestAsync.getRepositoryLanguages(username);
System.out.println(futureList.size());
}
In the service I use List<ListanbleFuture> which seems does not work, since it is asynchronous, in the controller method I cannot have the size of futureList to run a for loop on it for the callbacks.
public List<ListenableFuture> getRepositoryLanguages(String username){
return getRepositoryLanguages(username, getUserRepositoriesFuture(username));
}
private ListenableFuture getUserRepositoriesFuture(String username) throws HttpClientErrorException {
HttpEntity entity = new HttpEntity(httpHeaders);
ListenableFuture future = restTemplate.exchange(githubUsersUrl + username + "/repos", HttpMethod.GET, entity, String.class);
return future;
}
private List<ListenableFuture> getRepositoryLanguages(final String username, ListenableFuture<ResponseEntity<String>> future) {
final List<ListenableFuture> futures = new ArrayList<>();
future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
#Override
public void onSuccess(ResponseEntity<String> response) {
ObjectMapper mapper = new ObjectMapper();
try {
repositories = mapper.readValue(response.getBody(), new TypeReference<List<Repositories>>() {
});
HttpEntity entity = new HttpEntity(httpHeaders);
System.out.println("Repo size: " + repositories.size());
for (int i = 0; i < repositories.size(); i++) {
futures.add(restTemplate.exchange(githubReposUrl + username + "/" + repositories.get(i).getName() + "/languages", HttpMethod.GET, entity, String.class));
}
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable throwable) {
System.out.println("FAILURE in getRepositoryLanguages: " + throwable.getMessage());
}
});
return futures;
}
Should I use something like ListenableFuture<List> instead of List<ListenableFuture> ?
It seems like you have a List<ListenableFuture<Result>>, but you want a ListenableFuture<List<Result>>, so you can take one action when all of the futures are complete.
public static <T> ListenableFuture<List<T>> allOf(final List<? extends ListenableFuture<? extends T>> futures) {
// we will return this ListenableFuture, and modify it from within callbacks on each input future
final SettableListenableFuture<List<T>> groupFuture = new SettableListenableFuture<>();
// use a defensive shallow copy of the futures list, to avoid errors that could be caused by
// someone inserting/removing a future from `futures` list after they call this method
final List<? extends ListenableFuture<? extends T>> futuresCopy = new ArrayList<>(futures);
// Count the number of completed futures with an AtomicInt (to avoid race conditions)
final AtomicInteger resultCount = new AtomicInteger(0);
for (int i = 0; i < futuresCopy.size(); i++) {
futuresCopy.get(i).addCallback(new ListenableFutureCallback<T>() {
#Override
public void onSuccess(final T result) {
int thisCount = resultCount.incrementAndGet();
// if this is the last result, build the ArrayList and complete the GroupFuture
if (thisCount == futuresCopy.size()) {
List<T> resultList = new ArrayList<T>(futuresCopy.size());
try {
for (ListenableFuture<? extends T> future : futuresCopy) {
resultList.add(future.get());
}
groupFuture.set(resultList);
} catch (Exception e) {
// this should never happen, but future.get() forces us to deal with this exception.
groupFuture.setException(e);
}
}
}
#Override
public void onFailure(final Throwable throwable) {
groupFuture.setException(throwable);
// if one future fails, don't waste effort on the others
for (ListenableFuture future : futuresCopy) {
future.cancel(true);
}
}
});
}
return groupFuture;
}
Im not quite sure if you are starting a new project or working on a legacy one, but if the main requirement for you is none blocking and asynchronous rest service I would suggest you to have a look into upcoming Spring Framework 5 and it integration with reactive streams. Particularly Spring 5 will allow you to create fully reactive and asynchronous web services with little of coding.
So for example fully functional version of your code can be written with this small code snippet.
#RestController
public class ReactiveController {
#GetMapping(value = "/repositories")
public Flux<String> getUsername(#RequestParam(value = "username") String username) {
WebClient client = WebClient.create(new ReactorClientHttpConnector());
ClientRequest<Void> listRepoRequest = ClientRequest.GET("https://api.github.com/users/{username}/repos", username)
.accept(MediaType.APPLICATION_JSON).header("user-agent", "reactive.java").build();
return client.exchange(listRepoRequest).flatMap(response -> response.bodyToFlux(Repository.class)).flatMap(
repository -> client
.exchange(ClientRequest
.GET("https://api.github.com/repos/{username}/{repo}/languages", username,
repository.getName())
.accept(MediaType.APPLICATION_JSON).header("user-agent", "reactive.java").build())
.map(r -> r.bodyToMono(String.class)))
.concatMap(Flux::merge);
}
static class Repository {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
To run this code locally just clone the spring-boot-starter-web-reactive and copy the code into it.
The result is something like {"Java":50563,"JavaScript":11541,"CSS":1177}{"Java":50469}{"Java":130182}{"Shell":21222,"Makefile":7169,"JavaScript":1156}{"Java":30754,"Shell":7058,"JavaScript":5486,"Batchfile":5006,"HTML":4865} still you can map it to something more usable in asynchronous way :)