Is it possible to use Spring Retry with TestNg Spring context - java

I have TestNg tests and use AbstractTestNGSpringContextTests:
#ContextConfiguration(classes = ContextConfiguration.class)
public class BaseTest extends AbstractTestNGSpringContextTests {
.......
}
And enabled retry in my config class:
#Configuration
#EnableRetry
public class TestContextConfiguration {
...
}
I would like to retry send request method in my service client:
#Service
public class ApiClient {
#Retryable
public Response getUser(String name){
...
return response;
But it does not work, no retry happens when the method throwing exception.
At the same time it works with jUnit spring test runner. Also Spring AOP works properly with AbstractTestNGSpringContextTests.
What could be the problem?

Related

How to create and use controllers specifically for test?

I want to have my Spring test with context that contains a controller.
This controller is not visible outside the tests and serves only purpose of testing.
So when i perform request through mockMvc, this request reach a controller.
How should i define the controller? I don't want to place it outside test package, because i don't need it there.
You can mark your controller by the test profile and this bean doesn't instantiate in the production mode:
#Profile("test")
#RestController
#RequestMapping("/apiUrl")
public class TestController {
...
}
Also, if you use spring boot you can use the TestConfiguration immediately in your tests:
#SpringBootTest
#ExtendsWith(SpringExtension.class)
public class ApiTest {
#Test
void testApi() {
// send request to test API
}
#TestConfiguration
public static class TestConfig {
#RestController
#RequestMapping("/apiUrl")
public class TestController {
#GetMapping("/test")
public String test() {
return "STUB";
}
}
}
}

spring-boot - running junit test with two servers

I created with java spring-boot a server (“node”). There are a few instance of nodes, as the difference is by a configuration file for every node. For example:
node1.properties:
application.name=FullNode
receiving.server.addresses=tcp://localhost:8001
propagation.server.addresses=tcp://localhost:8002
recovery.server.address=http://localhost:8060
....
node 2.properties:
application.name=FullNode
receiving.server.addresses=tcp://localhost:6001
propagation.server.addresses=tcp://localhost:6002
recovery.server.address=http://localhost:8050
...
To test the process of sending data to the server, I wrote a JUnit test for the TransactionController.
TransactionController:
#RestController
#RequestMapping("/transaction")
public class TransactionController {
#Autowired
private TransactionService transactionService;
...
#RequestMapping(method = PUT)
public ResponseEntity<Response> addTransaction(#Valid #RequestBody
AddTransactionRequest addTransactionRequest) {
return transactionService.addNewTransaction(addTransactionRequest);
}
...
}
Test:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = AppConfig.class)
#SpringBootTest
public class DBTests {
#Autowired
private TransactionController transactionController;
#Test
public void addTransaction() {
transactionController.addTransaction(transactionRequest);
}
}
The problem is, that every node also sends his transactions to the other nodes. But How I could test it with JUnit? I could not just create in the test two instance of a TransactionController, because TransactionController is a spring bean singleton, and the only way to run a node, is with his configuration file.
How could I do it?
I would look at the Mockito framework. Mocking out the TransactionService. Instead of firing up 2 servers which lends itself to be more of an integration test instead of a unit test.

How to not connect to service when testing with Spring?

I have an application built with JHipster which contains several tests.
I created a simple configuration class that instantiate a bean connected to an external service as such :
#Configuration
public class KurentoConfiguration {
#Bean(name = "kurentoClient")
public KurentoClient getKurentoClient(#Autowired ApplicationProperties applicationProperties) {
return KurentoClient.create(applicationProperties.getKurento().getWsUrl());
}
}
But as you would guess, this code crash during testing because the external service is not up but this code is still run during application context loading.
So I need to create a "stateless" version of this bean to be used during testing.
Here is a simple example of a test that fail because of my configuration :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
}
#Test
public void getAllLogs()throws Exception {
restLogsMockMvc.perform(get("/management/logs"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
}
}
What is the solution to make this bean not highly dependent of an external service during unit testing ?
You can use the MockBean annotation in your test to replace your existing bean :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
#MockBean
private KurentoClient kurentoClient;
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
given(kurentoClient.someCall()).willReturn("mock");
}
....
}
Here is the Spring Boot documentation :
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans
Thanks to the help of everyone here, I managed to solve this problem :
I created an interface of KurentoClient and implemented a proxy that call KurentoClient methods
My "normal" #Bean kurentoClient returns the implemented proxy
I writed a #TestConfiguration (UnitTestConfiguration) and added a #Bean with the same signature as the one crated above but this one returns mockito's mock(KurentoClient.class)
I created a class TestBase that every test class extends and which
contains
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
#ContextConfiguration(classes = UnitTestConfiguration.class)
public class TestBase {
}

Spring's #Retryable not working when running JUnit Test

I have this test:
#RunWith(MockitoJUnitRunner.class)
public class myServiceTest {
#InjectMocks
myService subject;
private myService spy;
#Before
public void before() {
spy = spy(subject);
}
#Test
public void testing() {
when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
spy.print1();
verify(spy, times(3)).print2();
}
and then I have:
#Service("myService")
public class myService extends myAbstractServiceClass {
public String print1() {
String temp = "";
temp = print2();
return temp;
}
#Retryable
public String print2() {
return "completed";
}
}
then I have this interface(which my abstractService implements):
public interface myServiceInterface {
#Retryable(maxAttempts = 3)
String print1() throws RuntimeException;
#Retryable(maxAttempts = 3)
String print2() throws RuntimeException;
}
but, I get a runtimeexception thrown when I run the test, leading me to believe it is not retrying. Am I doing this wrong?
This is because you are not using the SpringJUnitClassRunner.
Mockito and your own classes are not taking the #Retryable annotation in account. So you rely on the implementation of Spring to do so. But your test does not activate Spring.
This is from the SpringJUnit4ClassRunner JavaDoc:
SpringJUnit4ClassRunner is a custom extension of JUnit's BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations.
To use this class, simply annotate a JUnit 4 based test class with #RunWith(SpringJUnit4ClassRunner.class) or #RunWith(SpringRunner.class).
You should restructure your test class at least to something like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MyConfig.class)
public class MyServiceTest {
#Configuration
#EnableRetry
#Import(myService.class)
public static class MyConfig {}
...
What am I doing there?
activate the Spring JUnit hook
specify the Spring context configuration class
define the spring configuration and import your service as a bean
enable the retryable annotation
Are there some other pitfalls?
Yes, you are using Mockito to simulate an exception. If you want to test this behaviour with Spring like this, you should have a look at Springockito Annotations.
But be aware of that: Springockito you will replace the spring bean completely which forces you to proxy the call of your retryable. You need a structure like: test -> retryableService -> exceptionThrowingBean. Then you can use Springockito or what ever you like e.g. ReflectionTestUtils to configure the exceptionThrowingBean with the behaviour you like.
You should reference the interface type of your service in your test: MyServiceInterface
And last but not least. There is a naming convention nearly all Java developers follow: class names have first letter of each internal word capitalized
Hope that helps.
Another way:
#EnableRetry
#RunWith(SpringRunner.class)
#SpringBootTest(classes={ServiceToTest.class})
public class RetryableTest {
#Autowired
private ServiceToTest serviceToTest;
#MockBean
private ComponentInsideTestClass componentInsideTestClass;
#Test
public void retryableTest(){
serviceToTest.method();
}
}
I think you should let Spring manage the bean, create the appropriate proxy and handle the process.
If you want to mock specific beans, you can create mocks and inject them to the service under test.
1st option could be unwrapping proxied service, creating mocks and manually injecting them:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class})
#DirtiesContext
public class TheServiceImplTest {
#Autowired
private TheService theService;
#Before
public void setUp(){
TheService serviceWithoutProxy = AopTestUtils.getUltimateTargetObject(theService);
RetryProperties mockRetryProperties = Mockito.mock(RetryProperties.class);
ReflectionTestUtils.setField(serviceWithoutProxy, "retryProperties", mockRetryProperties);
}
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
}
In this example, I mocked one bean, RetryProperties, and injected into the service. Also note that, in this approach you are modifying the test application context which is cached by Spring. This means that if you don't use #DirtiesContext, service will continue its way with mocked bean in other tests. You can read more here
Second option would be creating a test specific #Configuration and mock the depended bean there. Spring will pick up this new mocked bean instead of the original one:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class, TheServiceImplSecondTest.TestConfiguration.class})
public class TheServiceImplSecondTest {
#Autowired
private TheService theService;
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
#Configuration
static class TestConfiguration {
#Bean
public RetryProperties retryProperties() {
return Mockito.mock(RetryProperties.class);
}
}
}
In this example, we have defined a test specific configuration and added it to the #ContextConfiguration.

Testing Spring boot REST resource issues

There is a microservice made with Spring Boot. It consist of Jetty, Jersey, Jackson and Liquibase. One of tasks of this service is to receive some data via REST and return response. This operation goes through next units:
MyResource, #Component REST with JAX-RS annotations, that receives data and ask #Autowired MyService for response.
MyService, #Component service with #Autowired MyResource to ask it for response.
MyResource, simple #JpaRepository interface
This application works fine, but now I need to add some tests for every module. Usually I use Mockito to test units, so I test my service with mocked MyRepository (Mockito #Mock annotation + when() method). I want to test MyResource.java the same way.
I tried to use TestRestTemplate way to test with spring-boot-starter-test and my test class looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebIntegrationTest(randomPort = true)
public class MyResourceTest {
#Value("${local.server.port}")
private int port;
private String getBaseUrl() {
return "http://localhost:" + port;
}
#Test
public void test() {
final TestRestTemplate restTemplate = new TestRestTemplate();
assertEquals(restTemplate.postForEntity(getBaseUrl() + "/test", null, ResponseObject.class).getBody(), new ResponseObject());
}
}
And there are two problems. First - when my test is running, they run up whole spring application, so my liquibase scripts is trying to find database and this is a very long-time process. Second - I can't replace MyService class with Mockito proxy.
I tried to find some manuals about best practices in testing spring boot REST applications and I found MockMvc-based way, but it looks like don't run up server to run test. Can you please share your way to test REST resource in spring boot?
MockMvc is the prefered solution for your problem.
It runs the spring boot application but 'mocks' the request,so it does not really run over http but behaves as such.
You can get all beans of your application injected into your test class using #Autowired, so you can mock spring beans there.
I run all my tests over 6 classes of configuration/support.
AbstractTest to configure core of tests
#ActiveProfiles(resolver = TestActiveProfilesResolver.class)
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest
#SpringApplicationConfiguration(classes = Application.class)
public abstract class AbstractTest {
...
}
AbstractRepositoryTest to test my repositories over jdbc+jdbi
#Transactional
public abstract class AbstractRepositoryTest<R> extends AbstractTest implements Repositories {
#Inject
private ObjectMapper mapper;
#Inject
protected Repositories repositories;
private final Class<R> repositoryType;
...
}
AbstractServiceTest to test my services, this class contains the core of my service's test
public abstract class AbstractServiceTest {
...
}
AbstractIntegrationTest contains the core of my integration tests, utils methods to test controller's, etc.
public abstract class AbstractIntegrationTest extends AbstractTest {
...
}
Application provides to AbstractTest a context to run tests, this classe is same class that I use to run my spring boot application
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class);
}
...
}
And finnaly the TestActiveProfilesResolver, that provide the profile to match application-test.properties, It's necessary because exist the open issue on JIRA, here
public class TestActiveProfilesResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(final Class<?> testClass) {
final String activeProfile = System.getProperty("spring.profiles.active");
return new String[] {activeProfile == null ? "test" : activeProfile};
}
}
Sometimes my tests extend AbstractRepositoryTest, AbstractIntegrationTests or AbstractServiceTest, it depends on what I want.
This configuration solved all my problems to test services, controllers etc.

Categories