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
Related
I have configured the necessary Beans in #Configuration class but have not been able to get the RestTemplate injected into my test class for testing.
#Configuration
public class AppConfig {
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
#Bean
public RestTemplate restTemplate(ProtobufHttpMessageConverter converter) {
RestTemplate http2Template = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
List<HttpMessageConverter<?>> converters = http2Template.getMessageConverters();
converters.add(converter);
http2Template.setMessageConverters(converters);
return http2Template;
}
}
Test class:
#ExtendWith(SpringExtension.class)
#AutoConfigureWebClient(registerRestTemplate = true)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class})
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
Any advice and insight is appreciated
The classes attribute of the annotation #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class}) takes component classes to load the application context. You should not put in here anything except your main Spring Boot class or leave it empty.
Furthermore #AutoConfigureWebClient(registerRestTemplate = true) as you want to use the bean you configure inside your application (at least that's what I understood from your question).
So your test setup should look like the following:
// #ExtendWith(SpringExtension.class) can be omitted as it is already part of #SpringBootTest
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
This should now start your whole Spring Boot context in dev profile and you should have access to all your beans you define inside your production code like AppConfig.
I'm trying to write an integration test using springrunner. I'm running it as a springboottest and using autoconfiguremockmvc. However, i keep getting 404s. It seems my controllers/endpoints aren't being loaded by the autoconfiguremockmvc. Does anyone know a solution of how to wire this up so that it will pick up my controllers?
I did add a basic controller into my test class and i was able to hit it successfully but I have been unable so far to hit the actual controller I want to use in my integration test.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { TestConfig.class })
#TestPropertySource(locations = "classpath:application-test.properties")
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
MockHttpServletRequestBuilder request =
MockMvcRequestBuilders.post(URL).contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(json);
final ResponseEntity<String> response =
new ResponseEntity<>("works", HttpStatus.OK);
final ResultActions result = this.mockMvc.perform(request).andDo(print());
result.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
}
}
#TestConfiguration
#EnableAutoConfiguration
public class TestConfig {
}
You could use
#SpringBootTest(webEnvironment =
SpringBootTest.WebEnvironment.RANDOM_PORT)
and RestAssured library in combination to test any endpoint/api like below
// include dependency in pom
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.0.5</version>
<scope>test</scope>
</dependency>
//Test Class
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntTest{
//inject test local port
#LocalServerPort
private int port;
#Before
public void setUp() {
//assign port for reset assured
RestAssured.port = port;
}
#Test
public void test() {
given()
.get("/api")
.then()
.assertThat()
.statusCode(HttpStatus.OK.value())
.contentType(ContentType.JSON)
.body("name", is("test"));
}
}
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);
I have a Springboot application, where I have some Camel routes configured.
public class CamelConfig {
private static final Logger LOG = LoggerFactory.getLogger(CamelConfig.class);
#Value("${activemq.broker.url:tcp://localhost:61616}")
String brokerUrl;
#Value("${activemq.broker.maxconnections:1}")
int maxConnections;
#Bean
ConnectionFactory jmsConnectionFactory() {
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(new ActiveMQConnectionFactory(brokerUrl));
pooledConnectionFactory.setMaxConnections(maxConnections);
return pooledConnectionFactory;
}
#Bean
public RoutesBuilder route() {
LOG.info("Initializing camel routes......................");
return new SpringRouteBuilder() {
#Override
public void configure() throws Exception {
from("activemq:testQueue")
.to("bean:queueEventHandler?method=handleQueueEvent");
}
};
}
}
I want to test this route from activemq:testQueue to queueEventHandler::handleQueueEvent.
I tried different things mentioned here http://camel.apache.org/camel-test.html, but doesn't seem to get it working.
I am trying to do something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {CamelConfig.class, CamelTestContextBootstrapper.class})
public class CamelRouteConfigTest {
#Produce(uri = "activemq:testQueue")
protected ProducerTemplate template;
#Test
public void testSendMatchingMessage() throws Exception {
template.sendBodyAndHeader("testJson", "foo", "bar");
// Verify handleQueueEvent(...) method is called on bean queueEventHandler by mocking
}
But my ProducerTemplate is always null. I tried auto-wiring CamelContext, for which I get an exception saying it cannot resolve camelContext. But that can be resolved by adding SpringCamelContext.class to #SpringBootTest classes. But my ProducerTemplate is still null.
Please suggest. I am using Camel 2.18 and Spring Boot 1.4.
In Camel 2.22.0 and ongoing, which supports Spring Boot 2 you can use the following template to test your routes with Spring Boot 2 support:
#RunWith(CamelSpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
...
})
#EnableAutoConfiguration
#DisableJmx
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class RouteTest {
#TestConfiguration
static class Config {
#Bean
CamelContextConfiguration contextConfiguration() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext camelContext) {
// configure Camel here
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
// Start your manual routes here
}
};
}
#Bean
RouteBuilder routeBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:someEndpoint").to("mock:done");
}
};
}
// further beans ...
}
#Produce(uri = "direct:start")
private ProducerTemplate template;
#EndpointInject(uri = "mock:done")
private MockEndpoint mockDone;
#Test
public void testCamelRoute() throws Exception {
mockDone.expectedMessageCount(1);
Map<String, Object> headers = new HashMap<>();
...
template.sendBodyAndHeaders("test", headers);
mockDone.assertIsSatisfied();
}
}
Spring Boot distinguishes between #Configuration and #TestConfiguration. The primer one will replace any existing configuration, if annotated on a top-level class, while #TestConfiguration will be run in addition to the other configurations.
Further, in larger projects you might run into auto-configuration issues as you can't rely on Spring Boot 2 to configure your custom database pooling or what not correctly or in cases where you have a specific directory structure and the configurations are not located within a direct ancestor directory. In that case it is proabably preferable to omit the #EnableAutoConfiguration annotation. In order to tell Spring to still auto-configure Camel you can simply pass CamelAutoConfiguration.class to the classes mentioned in #SpringBootTest
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
RouteTest.Config.class,
CamelAutoConfiguration.class
}
As no automatic configuration is performed, Spring won't load the test configuration inside your test class nor initialize Camel as well. By adding those configs to the boot classes manually Spring will do it for you.
For one route with MQ and Spring Boot like this:
#Component
public class InboundRoute extends RouteBuilder {
#Override
public void configure() {
JaxbDataFormat personDataFormat = new JaxbDataFormat();
personDataFormat.setContextPath(Person.class.getPackage().getName());
personDataFormat.setPrettyPrint(true);
from("direct:start").id("InboundRoute")
.log("inbound route")
.marshal(personDataFormat)
.to("log:com.company.app?showAll=true&multiline=true")
.convertBodyTo(String.class)
.inOnly("mq:q.empi.deim.in")
.transform(constant("DONE"));
}
}
I use adviceWith in order to replace the endpoint and use only mocks:
#RunWith(CamelSpringBootRunner.class)
#UseAdviceWith
#SpringBootTest(classes = InboundApp.class)
#MockEndpoints("mock:a")
public class InboundRouteCamelTest {
#EndpointInject(uri = "mock:a")
private MockEndpoint mock;
#Produce(uri = "direct:start")
private ProducerTemplate template;
#Autowired
private CamelContext context;
#Test
public void whenInboundRouteIsCalled_thenSuccess() throws Exception {
mock.expectedMinimumMessageCount(1);
RouteDefinition route = context.getRouteDefinition("InboundRoute");
route.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveByToUri("mq:q.empi.deim.in").replace().to("mock:a");
}
});
context.start();
String response = (String) template.requestBodyAndHeader("direct:start",
getSampleMessage("/SimplePatient.xml"), Exchange.CONTENT_TYPE, MediaType.APPLICATION_XML);
assertThat(response).isEqualTo("DONE");
mock.assertIsSatisfied();
}
private String getSampleMessage(String filename) throws Exception {
return IOUtils
.toString(this.getClass().getResourceAsStream(filename), StandardCharsets.UTF_8.name());
}
}
I use the following dependencies: Spring Boot 2.1.4-RELEASE and Camel 2.23.2. The complete source code is available on Github.
This is how I did this finally:
#RunWith(SpringRunner.class)
public class CamelRouteConfigTest extends CamelTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(CamelRouteConfigTest.class);
private static BrokerService brokerSvc = new BrokerService();
#Mock
private QueueEventHandler queueEventHandler;
#BeforeClass
// Sets up an embedded broker
public static void setUpBroker() throws Exception {
brokerSvc.setBrokerName("TestBroker");
brokerSvc.addConnector("tcp://localhost:61616");
brokerSvc.setPersistent(false);
brokerSvc.setUseJmx(false);
brokerSvc.start();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new CamelConfig().route();
}
// properties in .yml has to be loaded manually. Not sure of .properties file
#Override
protected Properties useOverridePropertiesWithPropertiesComponent() {
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"),null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}
return null;
}
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry jndi = super.createRegistry();
MockitoAnnotations.initMocks(this);
jndi.bind("queueEventHandler", queueEventHandler);
return jndi;
}
#Test
// Sleeping for a few seconds is necessary, because this line template.sendBody runs in a different thread and
// CamelTest takes a few seconds to do the routing.
public void testRoute() throws InterruptedException {
template.sendBody("activemq:productpushevent", "HelloWorld!");
Thread.sleep(2000);
verify(queueEventHandler, times(1)).handleQueueEvent(any());
}
#AfterClass
public static void shutDownBroker() throws Exception {
brokerSvc.stop();
}
}
Did you try using Camel test runner?
#RunWith(CamelSpringJUnit4ClassRunner.class)
If you are using camel-spring-boot dependency, you may know that it uses auto configuration to setup Camel:
CamelAutoConfiguration.java
It means that you may also need to add #EnableAutoConfiguration to your test.
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.