Spring Boot testing with Junit - spring

Using Spring Boot 2.0.3.RELEASE
The goal of the test is have a service call a controller in the same app.
Here is the simplified setup I am trying
The app class
#SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplication.run(StartApp.class, args);
}
}
The controller class
#RestController
public class EmpCtrl {
private static final Logger logger = LoggerFactory.getLogger(EmpCtrl.class);
#Autowired
private EmpDao empDao;
#RequestMapping(value = "/emp01", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public List<Emp> findAllEmp01() {
logger.trace("running my.demo.controller.findAllEmp01");
List<Emp> emps = new ArrayList<>();
Iterable<Emp> results = this.empDao.findAll();
results.forEach(emp -> {emps.add(emp);});
return emps;
}
}
The service class
#Service
public class GetEmpSrv {
private static final Logger logger = LoggerFactory.getLogger(GetEmpSrv.class);
public void getEmps01(){
final String uri = "http://localhost:8080/emp01";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
logger.debug(result);
}
}
and the Junit class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = StartApp.class)
public class GetEmpSrvTest01 {
#Test
public void doCall() {
GetEmpSrv getEmpSrv = new GetEmpSrv();
getEmpSrv.getEmps01();
}
}
This is being run inside Eclipse Oxygen.3a (4.7.3a)
in the console it appears Spring Boot is running .. ic the load of h2 db and /emp01 is being mapped however in the Failure Trace of Junit ic
I/O error on GET request for "http://localhost:8080/emp01": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect
This makes me think the embedded Tomcat is not running. When I start Spring normally /emp01 returns a JSON as expected.
My question is: Is this type of testing possible with Junit? If so what do I need to do to make it work?

In your test, please autowire TestRestTemplate. The reason is that your spring test will run on another port and the call to http://localhost:8080 will fail.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = StartApp.class)
public class GetEmpSrvTest01 {
#Autowired
TestRestTemplate testRestTemplate;
#Test
public void doCall() {
// without http://localhost:8080, testRestTemplate it will handle it for you
testRestTemplate.getForObject("/emp01", String.class);
}
}
Also you can expect a list of objects in your test:
List<Emp> emps = testRestTemplate.getForObject("/emp01", List.class);

Related

how to create RestController in test directory for SpringBoot Application

Im currently writing integration test for SpringBoot Application .
It's functionality is to receive/send request from outside and forward/receive them to another application(APP_2). So there are two systems which needs to be mocked outside System and APP_2 .
HomeController
#Controller
public class HomeController {
#Autowired
ForwardController forwardController;
#RequestMapping("/")
public #ResponseBody
String greeting() {
return forwardController.processGET().toString();
}
}
ForwardController
#Service
public class ForwardController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment environment;
private ResponseEntity sendRequest(String url, HttpMethod method, HttpEntity requestEntity, Class responseType, Object... uriVariables) {
return restTemplate.exchange( url, method, requestEntity, responseType,uriVariables);
}
public ResponseEntity processGET()
{
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> entity = new HttpEntity<>(headers);
String app_2_url = environment.getProperty(Constants.APP_2_URL);
ResponseEntity<String> response = sendRequest(app_2_url,HttpMethod.GET,entity,String.class);
return response;
}
}
APP_2_CONTROLLER
#Controller
public class App_2_Controller {
#RequestMapping("/app2Stub")
public #ResponseBody
String greeting() {
return "Hello End of world";
}
}
Test Class which simulates the external request behavior to the system:
HTTP_request_Test
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = Application.class)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private Environment environment;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
String.class)).contains("Hello End of world");
}
}
Here in this test class I'm overriding the properties by having two property file. So when we run test the request would be sent to App_2_Controller ( Mock in my project ) rather than the real App .
QUESTION :
Is there any way to have the APP_2_CONTROLLER inside the test folder ? This is because I don't want to expose the unwanted test endpoint in my Actual application .
Here in the above project , Im changing the URL with properties. Is there a better way to put a controller for the same URL. For simplicity sake lets assume, app_2 url is app.com:9000/serve
Spring already comes with a MockRestServiceServer, that makes this a lot easier so that you don't have to create your own dummy controllers (App_2_Controller). So in your case, you can remove that controller, and write a test like this for ForwardController:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class ForwardControllerTest {
#Autowired
private RestTemplate restTemplate;
#Autowired
private ForwardController forwardController; // Your service
private MockRestServiceServer server;
#Before
public void setUp() {
server = MockRestServiceServer.bindTo(restTemplate).build();
}
#Test
public void processGet_returnsResponseFromAPI() {
server.expect(once(), requestTo("http://app.com:9000/serve"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("Hello End of world", MediaType.TEXT_PLAIN));
assertThat(forwardController.processGET().getBody()).isEqualTo("Hello End of world"));
}
}
Additionally, you can create a separate test for your actual controller (ForwardController is just a service), mock ForwardController and use MockMvc:
#RunWith(SpringRunner.class)
#WebMvcTest
public class HomeControllerTest {
#Autowired
private HomeController homeController;
#Autowired
private MockMvc mockMvc;
#MockBean
private ForwardController forwardController;
#Test
public void greeting_usesForwardController() {
when(forwardController.expectGET()).thenReturn("Hello End of world");
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello End of world")));
}
}
In this case, you'll end up with two tests:
One test to verify that RestTemplate is used to capture the proper response from your external REST API.
Another test to verify that HomeController just forwards whatever ForwardController responds.

Using #RestClientTest in spring boot test

I want to write a simple test using #RestClientTest for the component below (NOTE: I can do it without using #RestClientTest and mocking dependent beans which works fine.).
#Slf4j
#Component
#RequiredArgsConstructor
public class NotificationSender {
private final ApplicationSettings settings;
private final RestTemplate restTemplate;
public ResponseEntity<String> sendNotification(UserNotification userNotification)
throws URISyntaxException {
// Some modifications to request message as required
return restTemplate.exchange(new RequestEntity<>(userNotification, HttpMethod.POST, new URI(settings.getNotificationUrl())), String.class);
}
}
And the test;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}
But i get error that No qualifying bean of type 'org.springframework.web.client.RestTemplate' available. Which is right of course as i havn't mocked it or used #ContextConfiguration to load it.
Isn't #RestClientTest configures a RestTemplate? or i have understood it wrong?
Found it! Since i was using a bean that has a RestTemplate injected directly, we have to add #AutoConfigureWebClient(registerRestTemplate = true) to the test which solves this.
This was in the javadoc of #RestClientTest which i seem to have ignored previously.
Test which succeeds;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
#AutoConfigureWebClient(registerRestTemplate = true)
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}

Integration tests on spring-boot throws Connection refused

I have an unit test on Spring Boot:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Application.class)
public class CustomerControllerIT {
private RestTemplate restTemplate = new RestTemplate();
#Test
public void findAllCustomers() throws Exception {
ResponseEntity<List<Customer>> responseEntity = restTemplate.exchange(
"http://localhost:8080/Customer", HttpMethod.GET, null,
new ParameterizedTypeReference<List<Customer>>() {
});
List<Customer> list = responseEntity.getBody();
Assert.assertEquals(list.size(), 0);
}
}
If I launch test on started application - tests ok
If I try to launch only IT, there is connection refused error
My application.properties is same for single start.
For tests and located in resources and testResources.
Application.class is:
#ComponentScan({"mypackage"})
#EntityScan(basePackages = {"mypackage.model"})
#EnableJpaRepositories(basePackages = {"mypackage.persistence"})
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You must run a test with a running server ,
If you need to start a full running server, you can use random ports:
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
An available port is picked at random each time your test runs
You need this maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
Example:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class TestRest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void findAllCustomers() throws Exception {
ResponseEntity<List<Customer>> responseEntity = restTemplate.exchange(
"/Customer", HttpMethod.GET, null,
new ParameterizedTypeReference<List<Customer>>(){});
List<Customer> list = responseEntity.getBody();
Assert.assertEquals(list.size(), 0);
}
}
Please read the documentation for more reference
For running #SpringBootTest and JUnit5 (Jupiter) with static port you can use:
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class MyUnitTests {
#Test
void checkActuator() throws Exception {
final String url = "http://localhost:8080/actuator/health";
#SuppressWarnings("rawtypes")
ResponseEntity<Map> re = new RestTemplate().getForEntity(url, Map.class);
System.out.println(re);
assertEquals(HttpStatus.OK, re.getStatusCode());
re.getStatusCode();
}
}
If you're using JUnit 4:
Change: #ExtendWith(SpringExtension.class)
To: #RunWith(SpringRunner.class)
And then add the port property to your application.yml (inside folder src/main/resources):
server:
port: 8080
Or if have an application.properties:
server.port=8080
Reference:
How to configure port for a Spring Boot application
https://www.baeldung.com/spring-boot-change-port
Test the SpringBoot application startup

test spring 5 reactive rest service

I'm using SpringBoot 2 and Spring 5 (RC1) to expose reactive REST services. but I can't manage to write unit test for those controllers.
Here is my controller
#Api
#RestController
#RequestMapping("/")
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(path = "/", method = RequestMethod.GET)
public Flux<MyModel> getPages(#RequestParam(value = "id", required = false) String id,
#RequestParam(value = "name", required = false) String name) throws Exception {
return myService.getMyModels(id, name);
}
}
myService is calling a database so I would like not to call the real one. (I don't wan't integration testing)
Edit :
I found a way that could match my need but I can't make it work :
#Before
public void setup() {
client = WebTestClient.bindToController(MyController.class).build();
}
#Test
public void getPages() throws Exception {
client.get().uri("/").exchange().expectStatus().isOk();
}
But I'm getting 404, seems it can't find my controller
You have to pass actual controller instance to bindToController method.
As you want to test mock environment, you'll need to mock your dependencies, for example using Mockito.
public class MyControllerReactiveTest {
private WebTestClient client;
#Before
public void setup() {
client = WebTestClient
.bindToController(new MyController(new MyService()))
.build();
}
#Test
public void getPages() throws Exception {
client.get()
.uri("/")
.exchange()
.expectStatus().isOk();
}
}
More test examples you can find here.
Also, I suggest switching to constructor-based DI.

Camel Spring Boot MockEndpoint assertion not working as expected

My test application can start up normally with CamelSpringBootApplicationController. However, when I am working on the integration test, the assertion of MockEndpoint is not working as expected The snapshot of my test code is listed below. Am I doing anything wrong?
Application.java
#SpringBootApplication
public class Application {
....
public static final String DIRECT_BT_INPUT = "direct:btInput";
public static final String DIRECT_BT_OUTPUT = "direct:btOutput";
#Bean
public RouteBuilder RouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from(DIRECT_BT_INPUT).log("${body}").to(DIRECT_BT_OUTPUT);
from(DIRECT_BT_OUTPUT).log("done");
}
};
}
}
BTRouteUnitTest.java
#RunWith(CamelSpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#MockEndpoints(Application.DIRECT_BT_OUTPUT)
public class BTRouteIT {
#Autowired
protected CamelContext camelContext;
#EndpointInject(uri = "mock:" + Application.DIRECT_BT_OUTPUT)
protected MockEndpoint mockBtOutput;
#Produce(uri = Application.DIRECT_BT_INPUT)
protected ProducerTemplate producerTemplate;
#Test
public void test() throws InterruptedException {
mockBtOutput.expectedBodiesReceived("Hello");
producerTemplate.sendBody("Hello");
MockEndpoint.assertIsSatisfied(camelContext);
}
}
#MockEndpoint is not supported yet in Camel Spring Boot.
Workaround: move endpoint uris to properties file (in route definition use {{}}) and use different property file where you substitute original endpoint uri with mock:orginalUri.
You are testing with CamelSpringJUnit4ClassRunner in camel-test-spring. Camel spring test is for regular spring, not spring-boot.
Use SpringJUnit4ClassRunner test runner instead.

Resources