I'm trying to write a unit test for the following function.
responseHandler and ErrorHandler are both methods that are passes using the Command Pattern
and are used to continue the program's flow.
futureCommand is a lettuce object (Redis implementation in java).
I'm having difficulties with how to test this method since is both using a future and does not return anything.
public void getOfferData(EventRequestContext<? extends BaseEvent> ctx, int offerId, ResponseHandler<T1Offer> responseHandler,
ErrorHandler<Throwable> errorHandler) throws Exception {
String redisKey = keyPrefix + offerId;
RedisFuture<List<String>> futureCommand = connectionWrapper.getHashValues(redisKey, getRequiredParams());
futureCommand.thenAccept(valuesList -> {
TrackerScheduler.processT1GenreicPool.execute(ctx, () -> {
Map<String, String> resultMap = reconstructMapValues(valuesList, getRequiredParams(), redisKey, ctx);
T1Offer offerData;
if(!resultMap.isEmpty()) {
offerData = new T1Offer(resultMap);
} else {
offerData = new T1Offer();
}
if(!offerData.isValid()) {
errorHandler.onError(new Exception("Invalid fields in offerData"));
} else {
responseHandler.onResponse(offerData);
}
});
});
}
My best attempt was to send the assertion using the responseHandler method like this:
#Test
public void getOfferData_offerFullData_parseSuccess() throws Exception {
T1ClickOfferDao.instance.getOfferData(null, Integer.parseInt(OFF_ID), resultOffer -> {
Assert.assertEquals("", resultOffer.getActivationDate());
}, error -> {
});
}
but the Test context is finished before the future is evaluated. And even if I Threas.sleep for a second - the assertion does not affect the test result.
How about
#Test
public void getOfferData_offerFullData_parseSuccess() throws Exception {
final String lock = new String("lock");
T1ClickOfferDao.instance.getOfferData(null, Integer.parseInt(OFF_ID), resultOffer -> {
Assert.assertEquals("", resultOffer.getActivationDate());
synchronized(lock){
lock.notifyAll();
}
}, error -> {
});
synchronized(lock){
try{
lock.wait(1000*2);
} catch (InterruptedException ex) {
fail("Timeout");
}
}
}
Related
I am trying to make 3 rest calls using completablefutures and return for the first one that matches a specific response. Below is sample test code I wrote (Minus the rest calls) for it but that does not seem to work. I always see "future1" getting returned even with the wait time, which means, test2 and test3 are blocking. How do I achieve the ask?
I thought of using CompletableFuture.anyOf but that just returns the result for the first future that gets executed. Not the first one that matches a specified response. Please advise
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Testing {
public static void main(String args[]) throws InterruptedException, ExecutionException {
CompletableFuture<String> combinedFuture = test("future1", 10000)
.thenCompose(response1 -> test2()
.thenCompose(response2 -> test3()
.thenApply(response3 -> {
return combine(response1, response2, response3);
})));
System.out.println(combinedFuture.get());
}
private static CompletableFuture<String> test(String str, int i) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
return str;
});
}
private static CompletableFuture<String> test2() {
return test("future2", 0);
}
private static CompletableFuture<String> test3() {
return test("future3", 0);
}
private static String combine(String response1, String response2, String response3) {
String responseString = null;
if (response1 != null) {
return response1;
} else if (response2 != null) {
return response2;
} else if (response3 != null) {
return response3;
}
return responseString;
}
}
You need to have a specific task that will collect the result of the others and complete only when desired.
For instance:
public class Testing {
public static void main(String args[]) throws InterruptedException, ExecutionException {
String result = aggregator(
Arrays.asList(
test("future1", 10000),
test("future2", 0),
test("future3", 0)),
(value) -> { return value != null; },
"default value"
).get();
System.out.println(result);
}
private static CompletableFuture<String> aggregator(Collection<CompletableFuture<String>> tasks, Predicate<String> validator, String defaultValue)
{
CompletableFuture<String> a = new CompletableFuture<String>();
AtomicInteger count = new AtomicInteger(0);
tasks.forEach((t) -> {
t.whenComplete((value, error) -> {
int c = count.incrementAndGet();
if( error == null && validator.test(value) ) a.complete(value);
else if( c == tasks.size() ) a.complete(defaultValue);
});
});
return a;
}
private static CompletableFuture<String> test(String str, int i) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
return str;
});
}
}
The aggregator method will accept any number of other tasks and will compare the result of each using the validator provided. The first one that matches is returned immediately without waiting for the others.
And at the end, if none matched, it completes with the default value.
You can race the futures against each other and delegate completions to another one:
static <T> CompletableFuture<T> first(Stream<CompletableFuture<T>> futures) {
var delegate = new CompletableFuture<T>();
runAsync(() ->
futures.forEach(future ->
future.handle(
(value, error) -> {
if (value == null) {
return delegate.completeExceptionally(error);
} else {
return delegate.complete(value);
}
})));
return delegate;
}
The future returned by first completes (either successfully or with an error), whenever the first one passed in the futures argument completes.
Now
CompletableFuture<String> combinedFuture =
first(Stream.of(test("future1", 10000), test2(), test3()));
System.out.println(combinedFuture.get());
prints either "future2" or "future3" depending on which on happens to complete first.
I suppose to get the response from two API and then only move forward. To achieve this tried to use completableFuture but ending up in getting NullPointerException, when fetching response from 'result' object.
Infact, completeableFuture basically not have data.
Not able to debug the thread working directly.
public APIResult execute() throws InterruptedException, ExecutionException {
CompletableFuture<TaskChair> completableFutureChair = CompletableFuture.supplyAsync(()->new TaskChair(),executorChair);
CompletableFuture<TaskBottle> completableFutureBottle = CompletableFuture.supplyAsync(()->new TaskBottle(),executorChair);
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(completableFutureChair, completableFutureBottle);
combinedFuture.get();
TaskChair taskChair = completableFutureChair.get();
TaskBottle taskBottle = completableFutureBottle.get();
List<Chair> chairs = taskChair.getChairs();
List<Bottle> bottles = taskBottle.getBottles();
APIResult result = new APIResult(chairs, bottles);
return result;
}
class TaskChair implements Callable<List<Chair>>{
List<Chair> chairs;
public List<Chair> getChairs() {
return chairs;
}
public void setChairs(List<Chair> chairs) {
this.chairs = chairs;
}
#Override
public List<Chair> call() throws Exception {
chairs = new RestAPI().getChairs();
return chairs;
}
}
public static void main(String[] args) {
RestService service = new RestService();
APIResult result = null;
try {
result = service.execute();
} catch (InterruptedException | ExecutionException e) { }
System.out.println("Chair API Status -> ");
for(Chair chair:result.getChairs()) {
System.out.println(" id : "+chair.getId()+" name : "+ chair.getName());
}
}
I am trying to set a default value when exceptions happen in CompletableFuture I made it work by handle method as follows:
private static void testHandle() {
String name = null;
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> {
if (name == null) {
throw new RuntimeException("Computation error!");
}
return "Hello, " + name;
}).handle((s, t) -> s != null ? s : "Hello, Stranger!" + t.toString());
out.println(completableFuture.join());
}
But when I tried to stop the CompletableFuture using completeExceptionally when bad things happen and track the exception as follows I cannot catch the exception as I did just now.
private static void testCompleteExceptionally() {
String name = "Hearen";
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> {
delay(500L);
if (name == null) {
throw new RuntimeException("Computation error!");
}
return "Hello, " + name;
}).handle((s, t) -> {
try {
throw t.getCause();
} catch (Throwable e) {
out.println(e.toString()); // I was hoping to record the custom exceptions here;
}
return s != null ? s : "Hello, Stranger!" + t.toString();
});
if (name != null) {
completableFuture.completeExceptionally(new RuntimeException("Calculation failed!")); // when bad things happen, I try to complete it by exception;
}
out.println(completableFuture.join());
}
UPDATED 2018-06-09 Thanks for the help, #Daniele
private static void testCompleteExceptionally() {
String name = "Hearen";
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> {
delay(500L);
if (name == null) {
throw new RuntimeException("Computation error!");
}
return "Hello, " + name;
});
if (name != null) {
completableFuture.completeExceptionally(new RuntimeException("Calculation failed!"));
}
out.println(completableFuture.handle((s, t) -> s != null ? s : "Hello, Stranger!" + t.toString()).join());
}
The handle enclosed just before join() works as expected. But in this case, the returned value will be null.
Based on the handle API
Returns a new CompletionStage that, when this stage completes either normally or exceptionally, is executed with this stage's result and exception as arguments to the supplied function.
You are building a future, piping with an handle (so getting another future) and then completing exceptionally the future returned by the handle.
You should complete exceptionally the inner future itself, instead of the handle.
The point here is that handle returns another future; and you should not complete the "outer" future exceptionally, because doing that will bypass the handling behavior.
Below the code;
package stackOv;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Supplier;
public class TestHandle {
BiFunction<String, Throwable, String> handle2 = new BiFunction<String, Throwable, String>() {
#Override
public String apply(String s, Throwable t) {
try {
throw t.getCause();
} catch (Throwable e) {
// I was hoping to record the custom exceptions here;
System.out.println(e.toString());
}
return s != null ? s : "Hello, Stranger!" + t.toString();
}
};
private void testCompleteExceptionally() {
String name = "Hearen";
Supplier<String> supplier2 = () -> {
delay(500L);
if (name == null) {
throw new RuntimeException("Computation error!");
}
return "Hello, " + name;
};
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(supplier2);
if (name != null) {
// when bad things happen, I try to complete it by exception;
completableFuture.completeExceptionally(new RuntimeException("Calculation failed!"));
}
System.out.println(completableFuture.handle(handle2).join());
}
public static void main(String[] args) {
TestHandle th = new TestHandle();
th.testCompleteExceptionally();
}
private static void delay(long milli) {
try { Thread.sleep(milli); } catch (InterruptedException e) {}
}
}
I was trying to use netflix observable however I managed to do so only synchronously:
This is how I define the remote call:
#Named
public class BroConsumerService {
..
#HystrixCommand(fallbackMethod = "stubbedMethod")
public Observable<String> executeObservableBro(String name) {
return new ObservableResult<String>() {
#Override
public String invoke() {
return executeRemoteService(name);
}
};
}
private String stubbedMethod(String name) {
return "return stubbed";
}
//here I am actually invoking (and observing this method)
#RequestMapping("/executeObservableBro")
public String executeObservableBro(#RequestParam(value = "name", required = false) String name) throws ExecutionException, InterruptedException {
Observable<String> result= broConsumerService.executeObservableBro(name);
result.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
System.out.println("completed");
}
#Override
public void onError(Throwable e) {
System.out.printf(e.getMessage());
}
#Override
public void onNext(String s) {
System.out.println("on next..");
}
});
}
But that works synchronously. I want to be able to "listen" to the executeObservableBro before I execute it. and each time it's being executed ill get notified.
Example would be highly appreciated.
Thanks,
ray.
you have to provide schedulers in subscribeOn method like:
public static void main(String[] args) throws InterruptedException {
Observable<Integer> observable2 = Observable.create(subscriber->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Arrays.asList(1, 2, 3).forEach((value)-> subscriber.onNext(value));
subscriber.onCompleted();
subscriber.onError(new RuntimeException("error"));
});
System.out.println("Before");
observable2
.subscribeOn(Schedulers.io()).subscribe(
(next) -> log.info("Next element {}", next),
(error) -> log.error("Got exception", error),
() -> log.info("Finished")//on complete
);
System.out.println("After");
//Thread.sleep(5000); //uncomment this to wait for subscriptions, otherwise main will quit
}
Its not async by default :)
My apologies for throwing this random subject, but I did not come up with a better name,
class ReportSenderRunnable implements Runnable {
private final LPLogCompressor compressor;
public ReportSenderRunnable(final LPLogCompressor compressor) {
this.compressor = compressor;
}
#Override
public void run() {
executeTasks();
}
private void executeTasks() {
try {
// compressor.compress();
reportStatus = ReportStatus.COMPRESSING;
System.out.println("compressing for 10 seconds");
Thread.sleep(10000);
} catch (final IllegalStateException e) {
logCompressionError(e.getMessage());
} /*catch (final IOException e) {
logCompressionError(e.getMessage());
}*/ catch (InterruptedException e) {
logCompressionError(e.getMessage());
}
try {
reportStatus = ReportStatus.SENDING;
System.out.println("sending for 10 seconds");
Thread.sleep(10000);
} catch (final InterruptedException e) {
reportStatus = ReportStatus.EXCEPTION_IN_SENDING;
}
try {
reportStatus = ReportStatus.SUBMITTING_REPORT;
System.out.println("submitting report for 10 seconds");
Thread.sleep(10000);
} catch (final InterruptedException e) {
reportStatus = ReportStatus.EXCEPTION_IN_SUBMITTING_REPORT;
}
System.out.println("Report Sender completed");
reportStatus = ReportStatus.DONE;
}
private void logCompressionError(final String cause) {
logError(ReportStatus.COMPRESSING, cause);
reportStatus = ReportStatus.EXCEPTION_IN_COMPRESSION;
}
private void logError(final ReportStatus status, final String cause) {
LOGGER.error("{} - {}", status, cause);
}
}
Ideally, statements like
System.out.println("sending for 10 seconds");
Thread.sleep(10000);
will be replaced by actual tasks, but for now assuming this is the case, and they way it runs is
private void submitJob() {
final ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
final LPLogCompressor lpLogCompressor = getLpLogCompressor();
executorService.execute(getReportSenderRunnable(lpLogCompressor));
} catch (final IOException e) {
reportStatus = ReportStatus.EXCEPTION_IN_COMPRESSION;
LOGGER.debug("Error in starting compression: {}", e.getMessage());
}
System.out.println("started Report Sender Job");
}
My question was how to effectively test this code? The one I wrote is
#Test
public void testJobAllStages() throws InterruptedException, IOException {
final ReportSender reportSender = spy(new ReportSender());
doReturn(compressor).when(reportSender).getLpLogCompressor();
when(compressor.compress()).thenReturn("nothing");
reportSender.sendAndReturnStatus();
Thread.sleep(10);
assertEquals(ReportStatus.COMPRESSING, reportSender.getCurrentStatus());
Thread.sleep(10000);
assertEquals(ReportStatus.SENDING, reportSender.getCurrentStatus());
Thread.sleep(10000);
assertEquals(ReportStatus.SUBMITTING_REPORT, reportSender.getCurrentStatus());
}
This runs well for above code.
To me this is crappy for following reasons
Not all tasks would take same time in ideal cases
Testing with Thread.sleep will take too much time and also adds non-determinism.
Question
How do I test this effectively?
You could add a class with a method (e.g., TimedAssertion.waitForCallable) that accepts a Callable, which then uses an ExecutorService to execute that Callable every second until it returns true. If it doesn't return true in a specific period of time, it fails.
You would then call that class from your test like this:
boolean result;
result = new TimedAssertion().waitForCallable(() ->
reportSender.getCurrentStatus() == ReportStatus.COMPRESSING);
assertTrue(result);
result = new TimedAssertion().waitForCallable(() ->
reportSender.getCurrentStatus() == ReportStatus.SENDING);
assertTrue(result);
...etc. This way, you can easily wait for a particular state in your code to be true, without waiting too long -- and you can reuse this new class anywhere that you need this sort of assertion.
Based on #Boris the Spider comment, I made use of mocks and here is what my tests look like
#Mock
private ReportSenderRunnable reportSenderRunnable;
#Mock
private LPLogCompressor compressor;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test(timeout = 1000)
public void testJobNoException() throws InterruptedException, IOException {
final ReportSender reportSender = spy(new ReportSender());
doReturn(compressor).when(reportSender).getLpLogCompressor();
when(compressor.compress()).thenReturn("nothing");
reportSender.sendAndReturnStatus();
Thread.sleep(10);
assertEquals("Job must be completed successfully", ReportStatus.DONE,
reportSender.getCurrentStatus());
}
#Test(timeout = 1000)
public void testJobWithIllegalStateException() throws Exception {
final ReportSender reportSender = spy(new ReportSender());
doReturn(compressor).when(reportSender).getLpLogCompressor();
doThrow(IllegalStateException.class).when(compressor).compress();
reportSender.sendAndReturnStatus();
Thread.sleep(10);
assertEquals("Job must failed during compression", ReportStatus.EXCEPTION_IN_COMPRESSION,
reportSender.getCurrentStatus());
}