How to override Quarkus #Retry.delay in a unit test? - java

I have a method:
#Retry(retryOn = SomeException.class, maxRetries = 5, delay = 180000, maxDuration = 360000)
public void checkIfSomethingIsReady() {
if (something != ready) {
throw new SomeException();
} else {
// do stuff
}
}
I'm trying to do some boundary testing on a method like this without having to wait or retry. Is there a way to override this configuration solely for tests?

Yes you can,
MicroProfile Fault Tolerance also allows configuration using
MicroProfile Config. For example:
com.example.MyService/hello/Retry/delay=5
For the following code example:
#Singleton
public class MyService {
#Retry(maxRetries = 10, delay = 180000, retryOn = IOException.class)
public String hello() {
...
}
}
Therefore it would be packagePath.ClassName/methodName/Retry/delay=yourNumber
For tests, just have a different properties file with a different value.
Official Documentation: https://download.eclipse.org/microprofile/microprofile-fault-tolerance-3.0/microprofile-fault-tolerance-spec-3.0.html#_config_fault_tolerance_parameters
https://smallrye.io/docs/smallrye-fault-tolerance/5.0.0/usage/basic.html#_configuration

Related

Annotation with parameters already filled in

In the tech stack I'm working with we have an existing Java annotation for our unit tests which gets used like
#TeamTest(size = TestSize.Medium, timeout = 5000)
void myTest() {
// setup and asserts and stuff
}
Is there a way to setup a separate annotation that would already have the size and timeout variables stored? Ex:
#TeamTest(size = TestSize.Medium, timeout = 5000)
public static #interface MediumTest {}
That way I could just do
#MediumTest
void myTest() {
// setup and asserts and stuff
}

Java - Constructor Unit testing

I have following Java configuration class which I need to unit test using JUnit:
public class Config {
private static final String AMQ_CONNECTION_URL_TEMPLATE = "failover:(%s)";
private final String awsAmqUrl;
public Config(String url, Optional<String> amqConnectionOptions, PropertiesManager propertiesManager) {
String urlParameter = propertiesManager.getStringParameter(url);
this.awsAmqUrl = constructAmqConnectionString(urlParameter, amqConnectionOptions);
}
private String constructAmqConnectionString(String urlParameter, Optional<String> connectionOptions) {
if (connectionOptions.isPresent()) {
urlParameter = Stream.of(urlParameter.split(","))
.map(url -> url + "?" + connectionOptions.get())
.collect(Collectors.joining(","));
}
return String.format(AMQ_CONNECTION_URL_TEMPLATE, urlParameter);
}
public ConnectionFactory getConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(awsAmqUrl);
return connectionFactory;
}
}
I am struggling to find an optimal solution for constructAmqConnectionString method unit testing as it's marked as private.
There are 3 scenarios I am trying to cover:
urlParameter - comprises comma separated URLs (url1,url2),
connectionOptions is not empty;
urlParameter - comprises comma
separated URLs (url1,url2), connectionOptions is empty;
urlParameter - comprises single URL (url1), connectionOptions is
not empty.
Current solution is to add a getter into Config class for awsAmqUrl field so that logic of constructor's call can be verified/tested:
public String getAwsAmqUrl() {
return this.awsAmqUrl;
}
Tests itself have following logic:
#Test
public void verifyConstructorWithoutMqOptionsMultiBroker() {
when(propertiesManager.getStringParameter(any())).thenReturn("url1,url2");
Optional<String> amqConnectionOptions = Optional.empty();
config = new Config("url1,url2", amqConnectionOptions, propertiesManager);
assertEquals(String.format("failover:(url1,url2)"),config.getAwsAmqUrl());
}
#Test
public void verifyConstructorWithMqOptionsMultiBroker() {
when(propertiesManager.getStringParameter(any())).thenReturn("url1,url2");
Optional<String> amqConnectionOptions = Optional.of("optionTest=1");
config = new Config("url1,url2", amqConnectionOptions, propertiesManager);
assertEquals(String.format("failover:(url1?%1$s,url2?%1$s)",amqConnectionOptions.get()),config.getAwsAmqUrl());
}
#Test
public void verifyConstructorWithMqOptionsSingleBroker() {
when(propertiesManager.getStringParameter(any())).thenReturn("url1");
Optional<String> amqConnectionOptions = Optional.of("optionTest=1");
config = new Config("url1", amqConnectionOptions, propertiesManager);
assertEquals(String.format("failover:(url1?%1$s)",amqConnectionOptions.get()),config.getAwsAmqUrl());
}
Adding a getter just for Unit testing purposes doesn't feel the right thing to do as it's breaking encapsulation.
Is there a better way to approach testing in such scenario?
The only place that your class uses awsAmqUrl is in the getConnectionFactory method. So it looks like this is the method you'll have to use, to make sure the value of awsAmqUrl is correct. So instead of having a getter for awsAmqUrl, use something like
String storedUrl = objectUnderTest.getConnectionFactory().getBrokerUrl();
and then you can make assertions on that URL.
Sure, it makes your test dependent on the behaviour of ActiveMQConnectionFactory - but that's OK, since your class is tightly coupled to that particular class anyway.

Creating an annotation for JUnit 4/5 to initialize and inject an object in tests

I am developing a testing library for Kafka, Kafkaesque. The library lets you develop integration tests for Kafka using a fluid and elegant (?!) API. For now, I develop the version for Spring Kafka.
The library needs to be initialized in every test:
#Test
void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
kafkaTemplate.sendDefault(1, "data1");
kafkaTemplate.sendDefault(2, "data2");
new SpringKafkaesque(broker)
.<Integer, String>consume()
.fromTopic(CONSUMER_TEST_TOPIC)
.waitingAtMost(1L, TimeUnit.SECONDS)
.waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
.withDeserializers(new IntegerDeserializer(), new StringDeserializer())
.expecting()
.havingRecordsSize(2)
.assertingThatPayloads(Matchers.containsInAnyOrder("data1", "data2"))
.andCloseConsumer();
}
Instead of manually initializing the SpringKafkaesque object, I want to create an annotation that does the magic for me. Something like the #EmbeddedKafka annotation of Spring Kafka.
#SpringBootTest(classes = {TestConfiguration.class})
#Kafkaesque(
topics = {SpringKafkaesqueTest.CONSUMER_TEST_TOPIC, SpringKafkaesqueTest.PRODUCER_TEST_TOPIC})
class SpringKafkaesqueTest {
#Autowired
private Kafkaesque kafkaesque;
#Test
void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
kafkaTemplate.sendDefault(1, "data1");
kafkaTemplate.sendDefault(2, "data2");
kafkaesque
.<Integer, String>consume()
.fromTopic(CONSUMER_TEST_TOPIC)
.waitingAtMost(1L, TimeUnit.SECONDS)
.waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
.withDeserializers(new IntegerDeserializer(), new StringDeserializer())
.expecting()
.havingRecordsSize(2)
.assertingThatPayloads(Matchers.containsInAnyOrder("data1", "data2"))
.andCloseConsumer();
}
Is it possible? Any suggestion?
JUnit 4
One possible solution is to create a custom annotation processing using reflection. You can get the test method name with #Rule, so for example:
public class CustomAnnotationTest {
private SpringKafkaesque kafkaesqueInstance;
#Rule
public TestName testName = new TestName();
#Before
public void init() {
Method method = null;
try {
method = this.getClass().getMethod(testName.getMethodName());
} catch (Exception ex) {
// handle exceptions
}
if (method.isAnnotationPresent(EmbeddedKafka.class)) {
// Init your SpringKafkaesque instance here
// kafkaesqueInstance = new SpringKafkaesque(broker)
//
}
}
#EmbeddedKafka
#Test
public void testCustomAnnotated() {
// your test here
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#interface EmbeddedKafka {
}
}
You need to store this instance in the class-level variable. For the methods with no #EmbeddedKafka annotation, this variable will be null.
JUnit 5
With JUnit 5 you may consider using parameter injection with ParameterResolver. First of all, you need to implement this interface:
public class KafkaesqueResolver implements ParameterResolver {
#Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == SpringKafkaesque.class;
}
#Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
// Create an instance of SpringKafkaesque here and return it
return new SpringKafkaesque();
}
}
Next, add #ExtendWith(KafkaesqueResolver.class) annotation to your test class, and add a parameter to your test method, where you need the instance of SpringKafkaesque:
#ExtendWith(KafkaesqueResolver.class)
public class ParamInjectionTest {
#Test
public void testNoParams() {
// nothing to inject
}
#Test
public void testWithParam(SpringKafkaesque instance) {
// do what you need with your instance
}
}
No custom annotation required in this case.

Call different proxyied method from Spring Aspect

I have two different aspects. How do I ensure that when calling method from one aspect, it will still go through proxy chain?
Here is relevant code:
Inner aspect:
#Around("withinReplicatedRepository() && entityMethod() && insertMethod()")
public Object trackInsert(ProceedingJoinPoint jp) throws Throwable {
return trackChange(jp, ChangeType.INSERT, jp.getArgs()[0]);
}
Outer aspect:
#Around("withinReplicatedRepository() && entityMethod() && autoSaveRepository() && saveMethod()")
public Object saveEntity(ProceedingJoinPoint jp) throws Throwable {
TransactionUtil.ensureTransactional();
Object entity = jp.getArgs()[0];
AutoSaveRepository repository = (AutoSaveRepository)jp.getTarget();
if (repository.exists(entity)) {
repository.update(entity);
} else {
repository.insert(entity);
}
return null;
}
Usage:
AutoSaveRepository<MyEntity> repo = ...;
repo.save(entity);
My problem is that jp.getTarget() will return original class, thus repository.insert() will not be captured by trackInsert.
You can try
AopContext.currentProxy()
See Javadoc.
The prerequisite is that you activate proxy exposure, though:
In XML you can do this since Spring 3.0.3 via<aop:aspectj-autoproxy expose-proxy="true"/>.
In annotation-style config your can do it since 4.3.1 via #EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true),see Javadoc

Springboot #retryable not retrying

The following code is not retrying. What am I missing?
#EnableRetry
#SpringBootApplication
public class App implements CommandLineRunner
{
.........
.........
#Retryable()
ResponseEntity<String> authenticate(RestTemplate restTemplate, HttpEntity<MultiValueMap<String, String>> entity) throws Exception
{
System.out.println("try!");
throw new Exception();
//return restTemplate.exchange(auth_endpoint, HttpMethod.POST, entity, String.class);
}
I have added the following to the pom.xml.
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
I also tried providing different combinations of arguments to #Retryable.
#Retryable(maxAttempts=10,value=Exception.class,backoff=#Backoff(delay = 2000,multiplier=2))
Thanks.
In spring boot 2.0.2 Release, I have observed that the #Retryable is not working if you have retryable and called method in same class. On debugging found that the pointcut is not getting built properly. For now, the workaround for this problem is that we need to write the method in a different class and call it.
Working Example could be found here.
For the #Retryable annotation on the method to be discovered it needs to be called correctly from an initialised context. Is the method invoked from a bean from the spring context or called by other means?
If testing this is your runner using the SpringJunit4ClassRunner?
Spring's #Retryable, #Cacheable, #Transaction, etc. are ALL implemented using Aspect Oriented Programming. Spring implements AOP via proxy-based weaving. Proxies intercept calls from one bean to another. Proxies cannot intercept calls from one object's methods to another. This is a general limitation of proxy based weaving.
The following solutions address this limitation: 1) as mentioned above, use #Autowired (or #Resource) to inject a bean with a self reference; calls to this reference transit the proxy. 2) Use AspectJ's ClassLoader instead of Spring's default proxy-based weaving. 3) As mentioned above, place the methods on separate beans. I've done each in various situations, each has pros and cons.
I solved it. I figured out that if return something from the method that you trying to retry, then #Retryable() is not working.
maven dependency in pom.xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
Spring boot Application.java
#SpringBootApplication
#EnableTransactionManagement
#EnableRetry
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
in controller.java
#RestController
public class JavaAllDataTypeController {
#Autowired
JavaAllDataTypeService JavaAllDataTypeService;
#RequestMapping(
value = "/springReTryTest",
method = RequestMethod.GET
)
public ResponseEntity<String> springReTryTest() {
System.out.println("springReTryTest controller");
try {
JavaAllDataTypeService.springReTryTest();
} catch (Exception e) {
e.printStackTrace();
}
return new ResponseEntity<String>("abcd", HttpStatus.OK);
}
}
in service.java
#Service
#Transactional
public class JavaAllDataTypeService {
// try the method 9 times with 2 seconds delay.
#Retryable(maxAttempts=9,value=Exception.class,backoff=#Backoff(delay = 2000))
public void springReTryTest() throws Exception {
System.out.println("try!");
throw new Exception();
}
}
output: It' trying 9 times then throwing exception.
I had exactly the same issue as described in the original question.
In my case it turned out that the spring-boot-starter-aop dependency was accidentally not included. After adding it to my pom.xml, my #Retryable methods worked as expected.
Returning values from #Retryable methods works fine for me.
It work for return type as well
#Service
public class RetryService {
private int count = 0;
// try the method 9 times with 2 seconds delay.
#Retryable(maxAttempts = 9, value = Exception.class, backoff = #Backoff(delay = 2000))
public String springReTryTest() throws Exception {
count++;
System.out.println("try!");
if (count < 4)
throw new Exception();
else
return "bla";
}
}
For those who want to call #Retryable block in same class can to this way.
The key here is not to call the method directly and through self-injected bean
#Slf4j
#Service
public class RetryService {
#Resource(name = "retryService")
private RetryService self;
public String getValue(String appender) {
return self.getData(appender);
}
#Retryable(value = NumberFormatException.class, maxAttempts = 4, backoff = #Backoff(500))
public String getData(String appender) {
log.info("Calling getData");
Integer value = Integer.parseInt(appender);
value++;
return value.toString();
}
#Recover
public String recoverData(String appender) {
log.info("Calling recoverData");
return "DEFAULT";
}
}
Can read more about using Retry in detail here
An alternative could be RetryTemplate
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
and
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
#Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});
worked out for me
source
Pretty old thread, but I wanted to share that after changing my method visibility from private to public, Retryable was successfully retrying.
This is in addition to using the self resource mentioned above.
Even I faced the same issue, Later after some investigation and research came to know that along with #Retryable annotation above the method we also need to provide #EnableRetry above the class. This #EnableRetry annotation either can be provided above same class in to which you have provided method you want to retry or above your main spring boot application class. For example like this:
#RequiredArgsConstructor
**#EnableRetry**
#Service
public class SomeService {
**#Retryable(value = { HttpServerErrorException.class, BadRequestException.class},
maxAttempts = maxRetry, backoff = #Backoff(random = true, delay = 1000,
maxDelay = 8000, multiplier = 2))**
public <T> T get( ) throws HttpServerErrorException, BadRequestException {
//write code here which you want to retry
}
}
I hope this will help and resolve your issue.
I got this one solved by moving #Retryable directly in front of the method I wanted to retry.
From this:
public class MyClass {
public String toBeRetried() {
return delegateTo();
}
#Retryable
public String delegateTo() {
throw new Exception();
}
}
To this:
public class MyClass {
#Retryable
public String toBeRetried() {
throw new Exception();
}
}

Categories