I am using JUnit 5, running test with simple mvn clean test
. I have a few services/utilities that require some waiting in test, in order to test their functionality. One of them is a simple circuit breaker.
public class DefaultHttpCircuitBreaker implements CircuitBreaker<HttpResponse> {
private enum State {
CLOSED,
OPEN,
HALF_OPEN
}
private final int failureThreshold;
private final long retryPeriod;
private State state;
private int failureCount;
private long lastFailureTime;
private String lastFailureMessage;
public DefaultHttpCircuitBreaker(int failureThreshold, long retryPeriod) {
this.retryPeriod = retryPeriod;
this.state = State.CLOSED;
this.failureCount = 0;
this.failureThreshold = failureThreshold;
}
@Override
public RemoteServiceResponse<HttpResponse> attemptCall(Caller<HttpResponse> caller) {
evaluateState();
if (this.state == State.OPEN) {
return RemoteServiceResponse.failure(lastFailureMessage);
} else {
try {
HttpResponse response = caller.call();
int result = response.getStatusLine().getStatusCode();
if (result < 200 || result >= 300) {
recordFailure(response.getStatusLine().getReasonPhrase());
return RemoteServiceResponse.failure(lastFailureMessage);
}
resetCount();
return RemoteServiceResponse.success(response);
} catch (Exception e) {
recordFailure(e.getMessage());
return RemoteServiceResponse.communicationError(lastFailureMessage);
}
}
}
@Override
public long getLastFailureTime() {
return lastFailureTime;
}
@Override
public int getFailureCount(){
return failureCount;
}
private void recordFailure(String message) {
failureCount++;
this.lastFailureMessage = message;
this.lastFailureTime = System.currentTimeMillis();
}
private void evaluateState() {
if (failureCount >= failureThreshold) {
if ((System.currentTimeMillis() - lastFailureTime) > retryPeriod) {
this.state = State.HALF_OPEN;
} else {
this.state = State.OPEN;
}
} else {
this.state = State.CLOSED;
}
}
private void resetCount() {
this.failureCount = 0;
this.state = State.CLOSED;
lastFailureTime = 0;
}
}
As you can see, when the CircuitBreaker is open, and certain time has passed, it become half open, so it can attempt to do call once more.
And the test:
void testHalfOpenSuccess() throws InterruptedException {
int failureThreshold = 5;
String reasonPhrase = "testPhrase";
long retryPeriod = 10000;
StatusLine line = new BasicStatusLine(new ProtocolVersion("version", 1, 1), 300, reasonPhrase);
Mockito.when(response.getStatusLine()).thenReturn(line);
CircuitBreaker<HttpResponse> circuitBreaker = new DefaultHttpCircuitBreaker(failureThreshold, retryPeriod);
long lastFailureTime = 0;
for (int i = 0; i < failureThreshold; i++) {
RemoteServiceResponse<HttpResponse> res = circuitBreaker.attemptCall(() -> client.execute(new HttpGet("testUrl")));
lastFailureTime = circuitBreaker.getLastFailureTime();
Assertions.assertTrue(res.isFail());
Assertions.assertEquals(reasonPhrase, res.getErrMessage());
}
RemoteServiceResponse<HttpResponse> res = circuitBreaker.attemptCall(() -> client.execute(new HttpGet("testUrl")));
Assertions.assertTrue(res.isFail());
Assertions.assertEquals(reasonPhrase, res.getErrMessage());
Assertions.assertEquals(lastFailureTime, circuitBreaker.getLastFailureTime());
Thread.sleep(2 *retryPeriod); //NOSONAR
Mockito.when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("version", 1, 1), 200, reasonPhrase));
RemoteServiceResponse<HttpResponse> resSuccess = circuitBreaker.attemptCall(() -> client.execute(new HttpGet("testUrl")));
Assertions.assertTrue(resSuccess.isSuccess());
Assertions.assertEquals(0, circuitBreaker.getFailureCount());
Assertions.assertEquals(0, circuitBreaker.getLastFailureTime());
}
This test attempts to call service threshold times, all should fail. Then it tries to call it instantly one more time just it to fail. However, then i sleep the thread so the retryPeriod has passed and the CircuitBreaker can attempt to call once more.
This test works. However, when I run it with mvn clean test
it randomly fails 1 out of 10 times. What is reason for this? If I run this test using IntelliJ, it never fails. What is the reason maven fails test using Thread.sleep()? Same thing happens when I run test that uses wait()
and notify()
. However only the last defined test in class fails like this.
What fails is last Assertions.assertTrue(resSuccess.isSuccess())
;
What is the reason?