Spring boot #Retryable Mock test fails - java

Test case fails to retry multiple times (2), it tries once and the exception is thrown
my application class has EnableRetry annotation
#SpringBootApplication
#ComponentScan("com.***")
#EnableJpaAuditing
#EnableRetry
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
}
This is the service class that has the method I want to retry.
#Service
#RequiredArgsConstructor
#Slf4j
#EnableRetry //just in case, this didn't help though!!
public class DataSetService {
//some objects
private int count;
.....
#Scheduled(fixedRate = 86400000)
#Retryable(value = {OptimisticLockException.class}, maxAttemptsExpression = 2, backoff =
#Backoff(delayExpression = 1000))
public void updateDataSetExp() {
LOG.info("Count : " + count++); //Expecting this count to be 3 after re-tries
Date dateBeforeExpiry = new Date(System.currentTimeMillis() - THIRTY_DAYS_IN_MS);
List<DataSet> expiredDataSets =
dataSetRepository.findByCreatedAtBefore(dateBeforeExpiry);
for (DataSet expiredDs : expiredDataSets) {
expiredDs.setExpired(true);
dataSetRepository.save(expiredDs);
}
}
}
#Recover
public void updateDataSetExpFallback(OptimisticLockException e) {
LOG.error("Optimistic locking issues found");
}
My test case
#RunWith(SpringRunner.class)
#SpringBootTest
public class DataSetServiceRunnerTest {
#Autowired
private DataSetService dataSetService;
DataSetCreateRequest dataSetCreateRequest;
DataSet dataSet;
#MockBean
DataSetRepository dataSetRepository;
//all other mock beans that are required
#Before
public void setup(){
dataSetService = new DataSetService(dataReferenceRepository, dataSetRepository,
principalService,userInfoService, ccStorageService, elasticConfiguration,
clusterRepository, encryptionService, awsService);
dataSet = new DataSet();
}
#Test
public void testUpdateDataSetExpiryOptimisticLock(){
List<DataSet> expiredDataSetList = new ArrayList<>();
expiredDataSetList.add(dataSet);
when(dataSetRepository.findByCreatedAtBefore(any(Date.class)))
.thenReturn(expiredDataSetList);
when(dataSetRepository.save(any(DataSet.class)))
.thenThrow(OptimisticLockException.class);
try {
dataSetService.updateDataSetExp();
} catch (OptimisticLockException e){
System.out.println("Exception caught");
}
verify(dataSetRepository, times(3)).save(dataSet);
verify(dataSetRepository, times(3)).findByCreatedAtBefore(any(Date.class));
}
}
I am expecting dataSetRepository.findByCreatedAtBefore method to be invoked 3 times and then the exception is thrown but in my case, it is only invoked once. also, the count is expected to be 3 but is 1.
I've tried looking into other posts and followed the documentation around Retryable but didn't help. Can someone provide some pointers.

In your test case setup() is not required ideally because #SpringBootTest will load Spring context and all Beans will be initialized explicitly.
Also, I tried to replicate your setup and it seems to be working fine with and without #Scheduled
#Slf4j
#Service
#AllArgsConstructor
public class RetryDirectService {
private final TestService testService;
#Retryable(value = NumberFormatException.class, maxAttempts = 4, backoff = #Backoff(500))
#Scheduled(fixedRate = 10000)
public String getData() {
return testService.getData();
}
#Recover
public String recoverData(String appender) {
log.info("Calling recoverData");
return "DEFAULT";
}
}
#SpringBootTest
public class RetryDirectServiceTest {
#Autowired
private RetryDirectService retryDirectService;
#MockBean
private TestService testService;
#Test
public void testShouldRetryTillMaxAttemptsAndCallsRecoveryBlock(){
//Given
Mockito.when(testService.getData()).thenThrow(new NumberFormatException());
//When
retryDirectService.getData();
//Then
verify(testService, Mockito.times(4)).getData();
}
}
The only deviation was that with #Scheduled, getData() was called 5 times. Which is expected. 4 times for retry and one time when triggered by the scheduler.
For more detail in Retry setup plase refer this post

Related

Retryable annotation - Junit5 - Mockito - is it possible

Is it possible to write unit test using Junit 5 mockito for retryable annotations?
I am having a service interface which has only one method, which downloads the file from remote url
#service
interface downloadpdf{
#Retryable(value = { FileNotFoundException.class, HttpClientErrorException.class }, maxAttempts = 5, backoff = #Backoff(delay = 1000))
public string downloadpdffile(string remoteurl, string pdfname);
}
I have tried referring sites and found using Spring4JunitRunner implementation to test retry. Got confused with implementation. Is it possible to write unit test using Junit 5 mockito for retryable annotations?. Could you please elaborate on the solution here?
You need to use #SpringJUnitConfig (which is the equivalent of the JUnit4 runner). Or #SpringBootTest as you are using Boot.
#Retryable only works with beans managed by Spring - it wraps the bean in a proxy.
#SpringBootApplication
#EnableRetry
public class So71849077Application {
public static void main(String[] args) {
SpringApplication.run(So71849077Application.class, args);
}
}
#Component
class RetryableClass {
private SomeService service;
void setService(SomeService service) {
this.service = service;
}
#Retryable
void retryableMethod(String in) {
service.callme();
throw new RuntimeException();
}
#Recover
void recover(Exception ex, String in) {
service.failed();
}
}
interface SomeService {
void callme();
void failed();
}
#SpringBootTest
class So71849077ApplicationTests {
#MockBean
SomeService service;
#Test
void testRetry(#Autowired RetryableClass retryable) {
SomeService service = mock(SomeService.class);
retryable.setService(service);
retryable.retryableMethod("foo");
verify(service, times(3)).callme();
verify(service).failed();
}
}
I was also trying to implement this using Junit5.
Tried various options but that didn't help. Then after googling for few hours, got the following link and it helped to succeed.
https://doctorjw.wordpress.com/2022/04/29/spring-testing-a-single-bean-in-junit-5-springextension/
Reference code below, for detailed explanation, please refer the blog.
#Component
public class MyClass {
private ObjectMapper objectMapper;
private RestTemplate restTemplate;
#Value("${testValue:5}")
private int value;
#Retryable(....)
public void doStuff() throws SomeException {
...
}
}
What I’ve discovered is, if I declare my test class this way:
#ExtendWith( SpringExtension.class )
#Import( { MyClass.class, ObjectMapper.class } )
#EnableRetry
public class MyClassTest {
#Autowired
private MyClass myClass;
#MockBean
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#BeforeEach
public void setup() {
// If we are going to jack with the object configuration,
// we need to do so on the actual object, not the Spring proxy.
// So, use AopTestUtils to get around the proxy to the actual obj.
TestingUtils.setFieldValue( AopTestUtils.getTargetObject( myClass ), "value", 10 );
}
}
You will notice the inclusion of 1 other class, TestingUtils.class. This class looks like:
public class TestingUtils {
public static void setFieldValue( Object object, String fieldName, Object value ) {
Field field = ReflectionUtils.findField( object.getClass(), fieldName );
ReflectionUtils.makeAccessible( field );
ReflectionUtils.setField( field, object, value );
}
}
All credits goes to the author of the blog.

Why isn't spring boot's #Retryable being triggered when running unit test?

I created a basic set-up to try out Spring Retry, but it's not working for some reason. I think I have all of the annotations needed so I'm not sure what's wrong here. Any help would be appreciated.
In AppConfig.java:
#SpringBootApplication
#EnableRetry
public class AppConfig {
//run the spring application
}
In SomeClass.java:
#Component
public class SomeClass{
public SomeClass() { }
int counter = 0;
#Retryable(value = { SomeException.class }, maxAttempts = 3)
public void someMethod(){
counter++;
System.out.println(counter);
throw new SomeException("An exception was thrown!");
}
}
In TestSomeClass.java:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes=AppConfig.class)
public class TestSomeClass {
#Test
public void test() {
SomeClass obj = new SomeClass();
obj.someMethod();
}
}
When I run the test the counter will only print '1' and the exception is thrown, so it's not being re-run as expected.

Why doesn't my #Transactional method rollback when testing?

I have #transactional method that seems to be working (rolling back) if run the actual service and provide inputs that would cause a run-time error. If I create a Test for that method that throws a run-time error it doesn't seem to rollback anymore. Any idea why this doesn't work while testing?
it's somthing like:
#Service
public class SampleServiceImpl implements SampleService {
private final RepoA repoA;
private final RepoB repoB;
public SampleServiceImpl(RepoA repoA, RepoB repoB) {
this.repoA = repoA,
this.repoB = repoB
}
#Transactional
#Override
public void addItems() {
repoA.save(new ItemA(1,'name1')); //works
repoB.save(new ItemB(2,'name2')); //throws run-time error
}
}
#RunWith(SpringRunner.class)
#DataJpaTest
public class Tests {
#Autowired
private RepoA repoA;
#Mock
private Repob repoBMock;
#Test
public void whenExceptionOccurrs_thenRollsBack() {
var service = new SampleService(repoA, repoBMock);
Mockito.when(repoBMock.save(any(ItemB.class))).thenThrow(new RuntimeException());
boolean exceptionThrown = false;
try {
service.addItems()
} catch (Exception e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
Assert.assertFalse(repoA.existsByName('name1')); // this assertion fails because the the first item still exists in db
}
}
Just add annotation Rollback and set the flag to false.
#Test
#Rollback(false)

InterruptedException and TaskRejectedException in SpringBootTest with Async service

I am testing using SpringBootTest. One of the services behind uses an asynchronous service running with an Executor service that has 50 threads.
But I am getting Shutting Down ExecutorService asycnExecutor, InterruptedException then TaskRejectedException .... [shutting down pool size =8, active thread = 8] did not accept task.
Here the threadname is pool-3-thread-1 and parent is main i assume this is from the SpringBootTest. Also it seems 8 tasks have been created and accepted but shutdown after that
Also no message in InterruptedException when I caught it specifically. message is null;
I figured the following:
Something is shutting down the executor service when its running
Then it goes through interrupted exception and shutsdown
Rest of the tasks gets TaskRejectedException becuase executorservice
shutsdown
Sample Code (tried to simplify as much as possible):
#SpringBootApplication
#EnableCaching
#EnableScheduling
#EnableAsync
public class App extends springBootServletInitializer
{
public static voice main(String[] args){
SpringApplication.run(App.Class, args)
}
#Bean(name = "asycnExecutor")
public Executor asycnExecutor() {
ThreadPoolTaskExecutor exc = new ThreadPoolTaskExecutor();
//setcore = 50;
//setmaxpool = 1000
//setqueue = 2000
//setthreadnameprefix = "customAsycn"
//initialize
return exc;
}
}
NormalService.java
#Component
public class NormalService{
#Autowired
MyAsycnService myasyncService;
#Cacheable("myobject")
public MyObject someservice(){
do_someService();
}
#CachePut("myobject")
public MyObject do_someService(){
if(many){
/// some steps
foreach()
{
CompletableFuture<myobject> blah = myasyncService.doTask();
}
//allof / join / .get on the list of CompletableFuture<Void> and accumulate result
}
else {
///some otherstep
CompletableFuture<myobject> blah myasyncService.doTask();
//just 1 result
}
return myObject;
}
}
MyAsycnService.java
#Service
public class MyAsycnService {
#Autowired
RestTemplate restTemplate;
#Asynch("asycnExecutor")
public CompletableFuture<MyObject> doTask()
{
ResponseEntity<string> r = restTemplate.getEntity();
MyObject myobject = process(r);
return CompletableFuture.completedFuture(myobject);
}
private MyObject process(ResponseEntity<responseEntity> r)
{
MyObject o = new MyObject();
///someprcess
return o;
}
}
ScheduledControllerCallerConfig
#Component
pulic class ScheduledClass {
#Autowired
NormalService ns;
#Scheduled (fixedDelay = 10000, initialDelay = 500)
public void do_something()
{
ns.someservice();
}
}
MyController.Java
#Controller
public class MyController {
#AutoWired
NormalService ns;
#ApiOperation
#RequestMapping
public ResponseEntity<MyObject> getSomething()
{
MyObject = ns.someservice();
return ResponseEntity.ok(result);
}
}
Tests;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // also tried with classes = App.class
#Autowired
TestRestTemplate restTemplate
#Mock
MyObjectmyobject
#Test
public void testResponse() {
}
please help figure why it fails in the integration tests or some directions for debugging further?

Using autowired dependencies with certain mock dependency in Spring4

I have a rest resource for signup and login. both in a controller class. the controller class has a dependency to a service class with the business logic. the service class has further dependencies. cause i use an embedded db for testing, i want to use the real dependencies of my app instead to mock them with something like #injectmock #mock. there is only one certain dependency i have to mock. its the dependency for sending emails after a signup process. how to write test cases with #autowired function and one certain mock dependency for email notification?
#Controller
public class AccountCommandsController {
#Autowired
private LogoutService service;
#RequestMapping(value = "/rest/login", method = RequestMethod.POST)
public ResponseEntity login(#RequestBody Account account) {
AccountLoginEvent accountLoginEvent = service.loginAccount(new RequestAccountLoginEvent(account.getEmailAddress(), account.getPassword()));
if (accountLoginEvent.isLoginGranted()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else {
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
#RequestMapping(value = "/rest/signup", method = RequestMethod.POST)
public ResponseEntity signup(#RequestBody Account account) {
AccountSignupEvent signedupEvent = service.signupAccount(new RequestAccountSignupEvent(account.getEmailAddress(), account.getPassword()));
if (signedupEvent.isSignupSuccess()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else if (signedupEvent.isDuplicateEmailAddress()) {
return new ResponseEntity(HttpStatus.CONFLICT);
} else if (signedupEvent.isNoSignupMailSent()) {
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
} else {
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}
}
#Service
public class LogoutService {
#Autowired
private AccountsRepository accountsRepository;
#Autowired
private MailService mailService;
#Autowired
private HashService hashService;
public AccountSignupEvent signupAccount(RequestAccountSignupEvent signupEvent) {
if (accountsRepository.existEmailAddress(signupEvent.getEmailAddress())) {
return AccountSignupEvent.duplicateEmailAddress();
}
Account newAccount = new Account();
newAccount.setCreated(new Date());
newAccount.setModified(new Date());
newAccount.setEmailAddress(signupEvent.getEmailAddress());
newAccount.setPassword(signupEvent.getPassword());
newAccount.setVerificationHash(hashService.getUniqueVerificationHash());
SignupMailEvent mailSentEvent = mailService.sendSignupMail(new RequestSignupMailEvent(newAccount));
if (!mailSentEvent.isMailSent()) {
return AccountSignupEvent.noMailSent();
}
Account persistedAccount = accountsRepository.persist(newAccount);
return AccountSignupEvent.accountCreated(persistedAccount);
}
public AccountLoginEvent loginAccount(RequestAccountLoginEvent loginEvent) {
if (accountsRepository.existLogin(loginEvent.getEmailAddress(), loginEvent.getPassword())) {
return AccountLoginEvent.granted();
}
return AccountLoginEvent.denied();
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class LogoutTest {
private MockMvc mockMvc;
#Autowired
private AccountCommandsController controller;
#Before
public void setup() {
mockMvc = standaloneSetup(controller).build();
}
#Test
public void signupNoMail() throws Exception {
doReturn(AccountSignupEvent.noMailSent()).when(service).signupAccount(any(RequestAccountSignupEvent.class));
// when(controller.service.signupAccount(any(RequestAccountSignupEvent.class))).thenReturn(AccountSignupEvent.noMailSent());
mockMvc.perform(post("/rest/signup")
.content(new Gson().toJson(new Account(UUID.randomUUID().toString(), UUID.randomUUID().toString())))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isServiceUnavailable());
}
}
I hope you see the problem. Every dependency works fine instead mailservice. I dont want to use #injectmock and #mock with MockitoAnnotations.initMocks(this); in my test file, because of the neccessary to provide for all dependencies mocks.
if your dependencies are running and you have a configuration class where you have defined the endpoint, you can use ConfigurableApplicationContext class, something like this:
public class test {
private static ConfigurableApplicationContext appContext;
private LogoutService service;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
appContext = new AnnotationConfigApplicationContext(YourClassConfig.class);
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
}
Or you can re-write your endpoint with a configuration class and you can use WireMock (http://wiremock.org) to emulate your dependency with real data, this should be something like this:
public class test {
#Rule
public WireMockRule wireMockRule = new WireMockRule(15000);
private static ConfigurableApplicationContext appContext;
private LogoutService service;
private static String serviceMockUrl;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
serviceMockUrl = "http://localhost:" + wireMockRule.port();
appContext = new AnnotationConfigApplicationContext(TestConfig.class);
stubFor(get(urlEqualTo("urlToRequest")).
willReturn(aResponse().
withStatus(SC_OK).
withBody(createJsonArray("MapWithYourData").
withHeader("Content-Type", "application/json")));
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
#Configuration
static class TestConfig {
#Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer() {{
setProperties(new Properties() {{
setProperty("service.url", serviceMockUrl);
}});
}};
}
}
}
I hope this help you.
What you are trying to do is easily implemented using Spring Profiles.
On way to achieve it is the following:
#Configuration
public class TestConfiguration {
//this is the real mail service
#Bean
public MailService mailService() {
return new MailService(); //or whatever other bean creation logic you are using
}
//whatever else
}
#Configuration
#Profile("mockMail")
public class MockMailServiceConfig {
#Bean
#Primary
public MailService mockMailService() {
return mock(MailService.class);
}
}
Your test class would then look like:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
#ActiveProfiles("mockMail")
public class LogoutTest {
//do your testing
}
Note the use of #Primary in MockMailServiceConfig. I opted for this way since it wouldn't require you to introduce profiles anywhere else if you are not already using them. #Primary tells spring to use that specific bean if multiple candidates are available (in this case there is the real mail service and the mock service)

Categories

Resources