Mockito unit testing a retry logic - java

I have a piece of code that retries when it fails with ExceptionOne, and if it continues to fail, it will throw a ExceptionTwo. I want to test this behavior, but am not sure how to.
public void someMethod(String x) {
boolean retry;
int try = 0;
do {
retry = false;
try {
someFunction(x);
} catch (ExceptionOne e) {
if (try < 5) {
retry = true;
attempt++;
} else {
throw new ExceptionTwo();
}
}
} while (retry);
}
private void someFunction(String x) {
doSomething(); //This can throw ExceptionOne
}
Suppose these functions are inside a SomeClass. I mocked SomeClass and tried something like
doThrow(new ExceptionOne()).doNothing().when(mockObject).someMethod(any())
for consecutive calls, but this isn't quite what I am looking for since I believe this is really testing the someMethod, not someFunction or the retry functionality.
How do you test something like this? That is, how do I verify that the behavior that when someFunction fails with ExceptionOne, it will retry until it succeeds or until it runs out of retries and throws an ExceptionTwo?

I ended up scrapping my current retry logic and using an external library to do the retry instead.

Related

Simulate CompletionException in a test

I've got a class HttpClient that has a function that returns CompletableFuture:
public class HttpClient {
public static CompletableFuture<int> getSize() {
CompletableFuture<int> future = ClientHelper.getResults()
.thenApply((searchResults) -> {
return searchResults.size();
});
return future;
}
}
Then another function calls this function:
public class Caller {
public static void caller() throws Exception {
// some other code than can throw an exception
HttpClient.getSize()
.thenApply((count) -> {
System.out.println(count);
return count;
})
.exceptionally(ex -> {
System.out.println("Whoops! Something happened....");
});
}
}
Now, I want to write a test to simulates that ClientHelper.getResults fails, so for that I wrote this:
#Test
public void myTest() {
HttpClient mockClient = mock(HttpClient.class);
try {
Mockito.doThrow(new CompletionException(new Exception("HTTP call failed")))
.when(mockClient)
.getSize();
Caller.caller();
} catch (Exception e) {
Assert.fail("Caller should not have thrown an exception!");
}
}
This test fails. The code within exceptionally never gets executed. However, if I run the source code normally and the HTTP call does fail, it goes to the exceptionally block just fine.
How must I write the test so that the exceptionally code is executed?
I got this to work by doing this in the test:
CompletableFuture<Long> future = new CompletableFuture<>();
future.completeExceptionally(new Exception("HTTP call failed!"));
Mockito.when(mockClient.getSize())
.thenReturn(future);
Not sure if this is the best way though.

test multithreading (CompletableFuture) with EasyMock

I would like to add tests for method, which contains CompletableFuture:
public void report(List<String> srcList) {
if (srcList != null) {
...
CompletableFuture.runAsync(() ->
....
srcList.forEach(src-> downloader.send(url)));
}
}
I would like to test, that method send is called. My test looks like:
#Test
public void _test() {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
eventReporter.report(events);
verify(downloader);
}
And I get such error Downloader.send("http://xxxx//"): expected: 2, actual: 0
One way to avoid this error, is to set Thread.sleep(100); timeout. Then the thread will wait and verify that the method had called. But this will increase tests time.
Is there other way to test multithreading with EasyMock?
It is a bad practice to unit test asynchronoys code with Thread.sleep() method
because if it even works test will be unstable and flicker (run 3 times 2 pass and 1 fail)
if you set up big time of sleep and write few tests like this you meet a big time of execution
that may be exceed dozens of seconds. For complete this task you need to decouple asynchronous part
of you code from synchronous. Example how to do it:
class Service {
private Downloader downloader;
private ExecutorService service;
public Service (Downloader downloader, ExecutorService service) {
//set variables
}
public void doWork(List<String> list) {
for (String item : list) {
service.submit(() -> {
downloader.download(item);
});
}
}
}
ExecutorService is interface and we need to make our Service that will be synchronous
class SycnronousService impliments ExecutorService {
//methods empty implementations
public void submit(Runnable runnable) {
runnable.run(); //run immediately
}
//methods empty implementations
}
public class ServiceTest {
public void shouldPassAllItemsToDownloader() {
Downloader mockDownloader = AnyMockFramework.mockIt();
Service service = new Service(mockDownloader, new SycnronousService());
List<String> tasks = Arrays.asList("A", "B");
service.doWork(tasks);
verify(mockDownloader).download("A"); //verify in your way with EasyMock
verify(mockDownloader).download("B"); //verify in your way with EasyMock
// no more Timer.sleep() , test runs immeadetely
}
}
You need to replace CompletableFuture to something like in my example, because
unit testing this code not able in this way.
Later in you app you will be able to replace SycnronousService to asynchronous implementation and all will be work as expected.
I agree with #joy-dir 's answer. And you should probably do what she said to simplify your testing.
For the sake of completeness, your problem here is that the verify is called before your tasks being actually finished. There are many things you could do.
One is to loop on verify.
#Test
public void test() throws Exception {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
report(events);
for (int i = 0; i < 10; i++) {
try {
verify(downloader);
return;
} catch(AssertionError e) {
// wait until it works
}
Thread.sleep(10);
}
verify(downloader);
}
It won't sleep a long time for nothing when successful. However, you indeed need to make sure you wait enough to prevent the test from being flaky.
Another solution is actually to use the CompletableFuture returned by runAsync. I prefer this solution.
public CompletableFuture<Void> report(List<String> srcList) {
if (srcList != null) {
return CompletableFuture.runAsync(() -> srcList.forEach(src-> downloader.send(src)));
}
return CompletableFuture.completedFuture(null);
}
#Test
public void test2() throws Exception {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
CompletableFuture<Void> future = report(events);
future.get(100, TimeUnit.MILLISECONDS);
verify(downloader);
}
Finally, there is a hackish way. You ask the common pool if it is done. It is hackish because something else might use it. So it's cute but I wouldn't really recommend it.
#Test
public void test3() throws Exception {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
report(events);
while(!ForkJoinPool.commonPool().isQuiescent()) {
Thread.sleep(10);
}
verify(downloader);
}

test if a method behaves in a synchronized way

The below code is a snippet outlining the objective of this unit test. The below demonstrates createFile only performing one task which is already known to be a thread-safe operation.
The idea therefore is more around the test than the actual operation; to prove beyond a shadow of a doubt, the behavior of a thread-safe method in that it behaves in a way that we have already proven historically.
public static final synchronized void createFile(final File file) throws IOException {
file.createNewFile();
}
#Test
public void testCreateFileThreadSafety() throws Exception {
for (int i = 1; i < 50; i++) {
new Thread(new Runnable() {
#Override
public void run() {
try {
createFile(new File(i+".txt"));
new File(i+".txt").delete();
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}).start();
assertTrue(file.getParentFile().listFiles().length == 0);
}
}
EDIT:
What's happening now: Thread gets created, file gets created, file gets deleted, thread dies, assert proves nothing and repeat
What's expected: Threads should all start and assert should ensure that only one file gets created at a time and that the other threads are waiting, not executing the method
DOUBLE EDIT:
What I really need is a refactor of the above unit test so that it does what it's supposed to do (as mentioned above)
Of course for this very simple use case, it's quite silly, because the synchronized keyword is right there. But in general, if you want to test if a method is never called concurrently, you can throw in this:
static AtomicInteger c = new AtomicInteger();
public void knownNonThreadSafeMethod(final File file) throws IOException {
int t = c.incrementAndGet();
doSomething();
Thread.yield(); //try to force race conditions, remove in prod
assert t == c.intValue();
}
If you'd use a simple int i.s.o. the AtomicInteger, compiler optimizations would remove the assertion.
static int c = 0;
public void knownNonThreadSafeMethod(final File file) throws IOException {
int t = ++c;
doSomething();
assert t == c; //smart-ass compiler will optimize to 'true'
}
With AtomicInteger, the value is guaranteed to be synchronised over all CPU's and all threads, and thus you'll detect any concurrent accesses.
I know it's not in a JUnit test, but I couldn't find any non-intrusive way to fix this. Maybe you can inject it via AspectJ?
Create File subclass which overrides createNewFile method like this:
class TestFile extends File {
private final AtomicBoolean isCreated = new AtomicBoolean(false);
private boolean isSynchronizationFailed = false;
public boolean createNewFile() throws IOException {
if (isCreated.compareAndSet(false, true)) {
// give other threads chance to get here
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
// cleanup
isCreated.set(false);
} else {
isSynchronizationFailed = true;
}
return super.createNewFile();
}
}
Pass instance of this class to your threads
Assert at the end your test that isSynchronizationFailed is false.
If two threads somehow enter createNewFile method at the same time, you will have isSynchronizationFailed variable set to true.

JUnit test for an expected timeout

I have a test of communication over a TCP socket where I expect the server to not respond within a set time frame when I send a certain message.
The servers behaviour is nothing I can control.
I know how to fail a test if it has not completed within a set time frame. But how can I do the opposite, make it pass for not completing within the timeframe?
I can use #Test (timeout=1000) to make a test fail if not complete within a second.
But, using Junit 4, is there a function to test for an expected timeout as a positive result? I.e. The test will fail if completed within the time frame and pass if not?
Good question. Actually you can do this using only junit tools. My idea is to inverse Timeout rule behaviour + use expected attribute of Test annotation. The only one limitation: you have to place your test in separate class, because Rule applies to all tests inside it:
public class Q37355035 {
private static final int MIN_TIMEOUT = 100;
#Rule
public Timeout timeout = new Timeout(MIN_TIMEOUT) {
public Statement apply(Statement base, Description description) {
return new FailOnTimeout(base, MIN_TIMEOUT) {
#Override
public void evaluate() throws Throwable {
try {
super.evaluate();
throw new TimeoutException();
} catch (Exception e) {}
}
};
}
};
#Test(expected = TimeoutException.class)
public void givesTimeout() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
}
I'm building upon the great answer by Andremoniy, don't forget to upvote his answer if you like this one!
I used the following modification to skip a test if it doesn't finish in the expected time frame. The benefit of this is that the test will be marked as Skipped by JUnit instead of Successful. This is good for optimistic execution of tests which can sometimes hang or not finish fast enough, but you don't want to mark them as failed or delete them.
public class OptimisticTestClass {
private static final int TEST_METHOD_TIMEOUT_MS = 100;
#Rule
public Timeout timeout = new Timeout(TEST_METHOD_TIMEOUT_MS, TimeUnit.MILLISECONDS) {
public Statement apply(Statement base, Description description) {
return new FailOnTimeout(base, TEST_METHOD_TIMEOUT_MS) {
#Override
public void evaluate() throws Throwable {
try {
super.evaluate();
} catch (TestTimedOutException e) {
Assume.assumeNoException("Test did not finish in the allocated time, skipping!", e);
}
}
};
}
};
// The test times out and is skipped
public void givesTimeout() throws InterruptedException {
Thread.sleep(1000);
}
}
It's simpler in Java 9
CompletableFuture.supplyAsync(() -> dormammu.bargain())
.orTimeout(1, TimeUnit.SECONDS)
.handle((result, throwable) -> {
if (!(throwable instanceof TimeoutException)) {
Assertions.fail();
}
return result;
}).get();
If the method doesn't return within 1 second, it will time out. In handle method you can make sure TimeoutException was thrown, otherwise fail the test.

Controlling when an automation test ends - Espresso

I have an unusual situation I am testing. I am using Espresso to write my tests. I know Espresso and InstrumentationTestCase is not meant to do this.
I have a Listener I created in one of my classes that will notify me of a change of a certain value. I use the listener in my test suite.
When I get the value from the listener, I need to assert the value was changed as such.
My problem is the test will end before I will receive the value form the listener.
private void sendSpeedChanges() {
setStaticSpeed(new Random().nextInt(10) + 2);
try {
runTestOnUiThread(new Runnable() {
#Override
public void run() {
consoleActivity.onSpeedChanged(getStaticSpeed(), false);
}
});
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
private void createSpeedDelegate() {
EspressoMachineValues.setOnSpeedChangeListener(new EspressoMachineValues.OnSpeedChangeListener() {
#Override
public void onSpeedChanged(double speed) {
//assert speed is correct.
assertTrue(getStaticSpeed() == speed);
}
});
}
These are the two methods I am using. The createSpeedDelegate() is call at the very beginning. Then I call sendSpeedChanges. I need to do this X-amount of times.
NOTE:
It takes about 200 milliseconds to retrieve info (On average).
I can't call sendSpeedChanges() until I have checked the value in onSpeedChange()
I cannot use Thread.sleep(); because the listener is on the main thread.
I have tried adding a getInstrumentation().wait(2000); and also getInstrumentation().waitForIdleSync(); Obviously, neither work.
In a perfect world, I would do this:
for (int i = 0; i < 42; i++) {
sendSpeedChanges();
i++;
}
But, this will not wait for the value to be checked. And if I do wait for the value, the test runner thinks all tests are done and terminates.
My question is, would there be a way to control when the test quits? Even though my tests appear to be done.
Alternatively, you could create an IdlingResource to monitor your speed. IdlingResource is the way Espresso uses to verify the Main Thread is idle or that there is no AsyncTasks running in the AsyncTask pool.
Have a loook at coutingIdlingResource class in the library source code. You could implement something similar to that. In your case, it could be something like this:
[...]
private int mTargetSpeed;
private volatile ResourceCallback resourceCallback;
#Override
public boolean isIdleNow() {
boolean isIdle = false;
if (getStaticSpeed() == mTargetSpeed) {
isIdle = true;
resourceCallback.onTransitionToIdle();
} else {
isIdle = false;
}
return isIdle;
}
On your tests, whenever you want to wait for the speed to reach the desired level, you should do:
[...]
SpeedIdlingResource speedIdlingResource = new SpeedIdlingResource ("my_idling_resource");
Espresso.registerIdlingResources(speedIdlingResource);
This way, Espresso will block until your idling resource tells the framework your SpeedMonitor is idle. That's goode because you leverage all Espresso synchronization framework to monitor when you reached the target speed, so your tests would be more reliable and faster than making a occupied wait.
In your test you will need to create a control statement that keeps that test running as long as you want it to be run.
while(myTestNeedsToRun) {
if(speedChangeReceived) {
sendSpeedChange();
speedChangeReceived = false;
}
}
private void createSpeedDelegate() {
EspressoMachineValues.setOnSpeedChangeListener(new EspressoMachineValues.OnSpeedChangeListener() {
#Override
public void onSpeedChanged(double speed) {
//assert speed is correct.
assertTrue(getStaticSpeed() == speed);
speedChangedReceived = true;
}
});
Once you decide you are finished running your test just set myTestNeedsToRun = false and then your test will end.

Categories

Resources