How to specify a specfic port number for Spring WebTestClient - spring

I have a locally running rest endpoint I am attempting to communicate with using Spring WebClient. As a first step for testing purposes, I am trying to use Spring WebTestClient. My local rest endpoint runs on a specific port (lets say 8068). My assumption is that since the port is fixed, I am supposed to use:
SpringBootTest.WebEnvironment.DEFINED_PORT
, and then somehow specify what that port is in my code. However I do not know how to do that. It would appear to default to 8080. Here are the important parts of my code:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringWebclientApplicationTests {
#Autowired
private WebTestClient webTestClient;
#Test
public void wcTest() throws Exception {
String fullUri = "/services/myEndpoint/v1?userIds=jsmith&objectType=DEFAULT";
WebTestClient.ResponseSpec responseSpec1 = webTestClient.get().uri(fullUri, "").exchange().expectStatus().isOk();
}
This test expects to return "200 OK", but returns "404 NOT_FOUND". The request shown in the error response is:
GET http://localhost:8080/services/myEndpoint/v1?userIds=jsmith&objectType=DEFAULT
, obviously because it defaults to 8080, and I need to it to be 8068. I woould be grateful to anyone who can explain to define the port properly. Thanks.

I figured it out. I don't believe you are supposed to use
SpringBootTest.WebEnvironment.DEFINED_PORT
unless the endpoint is listening on 8080. In my case, since I needed to use a port number I could not control, i did this instead:
#RunWith(SpringRunner.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringWebclientApplicationTests {
private WebTestClient client;
#Before
public void setup() {
String baseUri = "http://localhost:" + "8079";
this.client = WebTestClient.bindToServer().baseUrl(baseUri).build();
}
#Test
public void wcTest() throws Exception {
String fullUri = "/services/myEndpoint/v1?userIds=jsmith&objectType=DEFAULT";
WebTestClient.ResponseSpec responseSpec1 = client.get().uri(fullUri, "").exchange().expectStatus().isOk();
}
}
, where I instantiate the webtestclient locally using the bindToServer method, and not as an autowired bean, and removing the:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
, so it works fine now.

Related

Springboot test controller via WebClient instead of WebTestClient

I'm attempting something perhaps misguided but please help.
I would like to test springboot controller via #WebFluxTest. However I would like to use WebClient instead of WebTestClient. How can this be done?
So far, I managed to use reflection to get ExchangeFunction out of WebTestClient and assign it to WebClient - and it works! Calls are made, controller responds - wonderful. However I don't think this is good approach. Is there a better way?
Thank you.
Ideally, you should use a WebTestClient which is more of a convenience wrapper around the WebClient. Just like the TestRestTemplate is for a RestTemplate. Both allow a request to be created and you can easily make assertions. They exist to make your test life easier.
If you really want to use a WebClient instead of a WebTestClient and do the assertions manually (which means you are probably complicating things) you can use the WebClient.Builder to create one. Spring Boot will automatically configure one and you can simply autowire it in your test and call the build method.
#SpringBootTest
public void YourTest {
#Autowired
private WebClient.Builder webClientBuilder;
#Test
public void doTest() {
WebClient webClient = webClientBuilder.build();
}
}
The same should work with #WebFluxTest as well.
Ok, after much experimentation here is a solution to test springboot controller & filters via a mocked connection - no webservice, no ports and quick test.
Unfortunately I didn't work out how to do it via #WebFluxTest and WebClient, instead MockMvc can be used to achieve desired result:
#ExtendWith(SpringExtension.class)
#Import({SomeDependencyService.class, SomeFilter.class})
#WebMvcTest(controllers = SomeController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class)
#AutoConfigureMockMvc()
public class SomeControllerTest {
#MockBean
private SomeDependencyService someDependencyService;
#Autowired
private MockMvc mockMvc;
private SomeCustomizedClient subject;
#BeforeEach
public void setUp() {
subject = buildClient();
WebClient webClient = mockClientConnection();
subject.setWebClient(webClient);
}
private WebClient mockClientConnection() {
MockMvcHttpConnector mockMvcHttpConnector = new MockMvcHttpConnector(mockMvc);
WebClient webClient = WebClient.builder().clientConnector(mockMvcHttpConnector).build();
return webClient;
}
#Test
public void sample() {
when(SomeDependencyService.somePersistentOperation(any(), any())).thenReturn(new someDummyData());
SomeDeserializedObject actual = subject.someCallToControllerEndpoint("example param");
assertThat(actual.getData).isEquals("expected data");
}
}
Now it is possible to test your customized client (for example if you have internal java client that contains few important customization like security, etags, logging, de-serialization and uniform error handling) and associated controller (and filters if you #import them along) at the cost of a unit test.
You do NOT have to bring up entire service to verify the client and controller is working correctly.

Passing an external property to JUnit's extension class

My Spring Boot project uses JUnit 5. I'd like to setup an integration test which requires a local SMTP server to be started, so I implemented a custom extension:
public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {
private GreenMail smtpServer;
private final int port;
public SmtpServerExtension(int port) {
this.port = port;
}
#Override
public void beforeAll(ExtensionContext extensionContext) {
smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
}
#Override
public void afterAll(ExtensionContext extensionContext) {
smtpServer.stop();
}
}
Because I need to configure the server's port I register the extension in the test class like this:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class EmailControllerIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Value("${spring.mail.port}")
private int smtpPort;
#RegisterExtension
// How can I use the smtpPort annotated with #Value?
static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content("some content")
).andExpect(status().isOk());
}
}
While this is basically working: How can I use the smtpPort annotated with #Value (which is read from the test profile)?
Update 1
Following your proposal I created a custom TestExecutionListener.
public class CustomTestExecutionListener implements TestExecutionListener {
#Value("${spring.mail.port}")
private int smtpPort;
private GreenMail smtpServer;
#Override
public void beforeTestClass(TestContext testContext) {
smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
};
#Override
public void afterTestClass(TestContext testContext) {
smtpServer.stop();
}
}
The listener is registered like this:
#TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
When running the test the listener gets called but smtpPort is always 0, so it seems as if the #Value annotation is not picked up.
I don't think you should work with Extensions here, or in general, any "raw-level" JUnit stuff (like lifecycle methods), because you won't be able to access the application context from them, won't be able to execute any custom logic on beans and so forth.
Instead, take a look at Spring's test execution listeners abstraction
With this approach, GreenMail will become a bean managed by spring (probably in a special configuration that will be loaded only in tests) but since it becomes a bean it will be able to load the property values and use #Value annotation.
In the test execution listener you'll start the server before the test and stop after the test (or the whole test class if you need that - it has "hooks" for that).
One side note, make sure you mergeMode = MergeMode.MERGE_WITH_DEFAULTS as a parameter to #TestExecutionListeners annotation, otherwise some default behaviour (like autowiring in tests, dirty context if you have it, etc) won't work.
Update 1
Following Update 1 in the question. This won't work because the listener itself is not a spring bean, hence you can't autowire or use #Value annotation in the listener itself.
You can try to follow this SO thread that might be helpful, however originally I meant something different:
Make a GreenMail a bean by itself:
#Configuration
// since you're using #SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there
public class MyTestMailConfig {
#Bean
public GreenMail greenMail(#Value(${"spring.mail.port"} int port) {
return new GreenMail(port, ...);
}
}
Now this configuration can be placed in src/test/java/<sub-package-of-main-app>/ so that in production it won't be loaded at all
Now the test execution listener could be used only for running starting / stopping the GreenMail server (as I understood you want to start it before the test and stop after the test, otherwise you don't need these listeners at all :) )
public class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.start();
}
#Override
public void afterTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.stop();
}
}
Another option is autowiring the GreenMail bean and using #BeforeEach and #AfterEach methods of JUnit, but in this case you'll have to duplicate this logic in different Test classes that require this behavour. Listeners allow reusing the code.

Spring Boot #RestClientTest how test with real server (not mock)

I need to create an integration test against a REST API. My service is using Resttemplate as HTTP client. The client code is generated from swagger file.
Running the test yields an error java.lang.AssertionError: No further requests expected: HTTP GET
It seems that the test is running against a mock server. How to let the test run against the real server?
This is my current test setup (want to cut out a minimal test frame to get a fast test - booting the complete context is far too slow):
#RunWith(SpringRunner.class)
#Import(value = { TpzConfig.class, TpzServiceRestImpl.class, ManufacturingPlantPhPmMapperImpl.class,
ProductHierarchyMapperImpl.class, PlantMapperImpl.class })
#ActiveProfiles(profiles = { "tpz" })
#RestClientTest
public class TpzServiceRestImplTest {
#Autowired
private TpzService to;
#MockBean
private ProductionPlantService ppService;
#MockBean
private ProductHierarchyService phService;
#Test
public void test() {
List<ProductManufacturer> pmByProductHierarchy = to.pmByProductHierarchy("001100909100100388");
}
}
I need #RestClientTest to have a bean of RestTemplateBuilder.
Is there a way to configure #RestClientTest to use the real server (similar to #DataJpaTest where i can configure not to use h2)?
#RestTemplateTest give you pre-configured RestTemplateBuilder and MockRestServiceServer.
1.You could #Autowired MockRestServiceServer and mock expected HTTP calls.
2.Remove the auto configuration :
#RestClientTest(excludeAutoConfiguration = MockRestServiceServerAutoConfiguration.class)
But that make the test kind of slow.. There is maybe a way to optimize it.
3.In another hand, you could remove #RestClientTest and in a test configuration file, create a bean of RestTemplateBuilder. Something like this :
#TestConfiguration
public class TestConfig {
#Bean
public RestTemplateBuilder getRestTemplateBuilder() {
return new RestTemplateBuilder();
}
}
After this, add this configuration file in your imports :
#Import(value = { TpzConfig.class, TpzServiceRestImpl.class,
ManufacturingPlantPhPmMapperImpl.class, ProductHierarchyMapperImpl.class,
PlantMapperImpl.class, TestConfig.class })
And you should be good for your test.

How to debug a Spring Boot application via SpringBootTest

I'm new to Spring Boot and I really like it especially when it comes to eliminate the boilerplate code.
I have created a test class to test my NBRController:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NewBusinessRevitalizationApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#TestPropertySource(properties = {"management.port=0"})
public class NBRControllerTest extends TestCase {
#LocalServerPort
private int port;
#Value("${local.management.port}")
private int mgt;
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void getApplicationByAgencyIdAndStatusTest() {
String uri = "http://localhost:" + this.port + "/nbr-services/applications/{status}?agencyIds=123456,56765,678576";
Map<String, String> vars = new HashMap<String, String>();
vars.put("status", "SAVED");
ResponseEntity<String> response = testRestTemplate.getForEntity(uri, String.class, vars);
assertEquals(HttpStatus.OK, response.getStatusCode());
}
}
If I run it in debug mode I can only debug the Test class and not my NBRController class:
#RestController
#RequestMapping("/nbr-services")
public class NBRController {
#Autowired
private NBRServices nbrServices;
private static Logger logger = LoggerFactory.getLogger(NBRController.class);
#RequestMapping(value = "/configuration/environment/{environment}", method = RequestMethod.GET)
#ResponseBody
public String getConfiguration(#PathVariable("environment") String environment) throws RemoteException {
logger.debug("environment={}", environment);
String result = nbrServices.getConfiguration(environment);
return result;
}
}
I have tried to setup the Tomcat debug port but not luck.
The only way I can debug my NBRController is run it in debug mode and call my RestAPI from the browser, but I want to use my unit test. Thanks in advance!
I had this happening when I accidentally had 2 controller methods with the same path mapping.
Other alternatives for debugging:
Only mock the server using mockMVC
It is possible to debug a system by not using a split webEnvironment, but to use spring MockMVC to make direct method calls to controllers instead of http calls.
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK // this is the default
)
#AutoConfigureMockMvc
class MyTest {
#Autowired
private MockMvc mockMvc;
#Test public void myTest() {
mockMvc.perform("/mypath");
// ...
}
}
This will not actually make http calls between the jUnit class and the controller, so this http processing part would not be tested.
Start the server separately and attach a remote debugger
In the IDE, start the application can in debugging mode
When application it is up and running, start JUnit tests which contain any http client, e.g. RestAssured.
This will spawn 2 JVMs, but the IDE is connected to both, so all breakpoints work.
I am using Intellij 2020.3 and I could able to debug my controller.
Stop all the running instances from intellij.
2.Simply put debug pointers in right controller method and run your test case in debug mode.
Unless you are hitting wrong endpoint, it should work.
Also you can try to evaluate your testRestTemplate call in test case in debug mode, in case it is failing in network itself.
You're possibly running on a different port to the one you think you are.
The SpringBootTest annotation is what controls the test port e.g.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
In the Uri you are appending http://local host +port , which is not necessary , testRestTemplate does it for you. Remove that and try you may hit the debug point
Here is example to write Junit for Rest layer for Spring boot 2 + JUnit 5
#ExtendWith(MockitoExtension.class)
public class RestTest {
#InjectMocks
private Rest rest;
private MockMvc mockMvc;
#BeforeEach
public void init() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(rest).build();
}
#Test
public void getTest() throws Exception {
String url = "/test";
ResultActions resultActions = mockMvc.perform(get(url));
resultActions.andExpect(status().isOk());
}
}
#RestController
#RequestMapping(value = "/test", produces = MediaType.APPLICATION_JSON_VALUE)
public class Rest {
#GetMapping
public #ResponseBody String get() {
return "success";
}
}
Personally, i use maven-spring-boot plugin and when i debug, its from a Maven run config. Maybe that is what is wrong with what you are doing? The maven-spring-boot plugin, during test phase, will start spring boot server before the test runs.
If you want to do it with a command line app , then you have to manually load the Spring context and execute the main class from a couple lines of code, before your test runs. I don't remember off-hand how to do it.

Mock external server during integration testing with Spring

I have a Spring web server that on a request makes an external call to some third-party web API (e.g. retreive Facebook oauth token). After getting data from this call it computes a response:
#RestController
public class HelloController {
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
// Ask facebook about something
HttpGet httpget = new HttpGet(buildURI("https", "graph.facebook.com", "/oauth/access_token"));
String response = httpClient.execute(httpget).getEntity().toString();
// .. Do something with a response
return response;
}
}
I'm writing an integration test that checks that hitting url on my server leads to some expected result. However I want to mock the external server locally so that I don't even need internet access to test all this. What is the best way to do this?
I'm a novice in spring, this is what I have so far.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest({})
public class TestHelloControllerIT {
#Test
public void getHelloToFacebook() throws Exception {
String url = new URL("http://localhost:8080/hello_to_facebook").toString();
//Somehow setup facebook server mock ...
//FaceBookServerMock facebookMock = ...
RestTemplate template = new TestRestTemplate();
ResponseEntity<String> response = template.getForEntity(url, String.class);
assertThat(response.getBody(), equalTo("..."));
//Assert that facebook mock got called
//facebookMock.verify();
}
}
The actual real set up is more complicated - I'm making Facebook oauth login and all that logic is not in the controller but in various Spring Security objects. However I suspect that testing code is supposed to be the same since I'm just hitting urls and expect a response, isn't it?
After playing a bit with various scenarios, here is the one way how can one achieve what was asked with minimal interventions to the main code
Refactor your controller to use a parameter for thirdparty server address:
#RestController
public class HelloController {
#Value("${api_host}")
private String apiHost;
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
// Ask facebook about something
HttpGet httpget = new HttpGet(buildURI("http", this.apiHost, "/oauth/access_token"));
String response = httpClient.execute(httpget).getEntity().toString();
// .. Do something with a response
return response + "_PROCESSED";
}
}
'api_host' equals to 'graph.facebook.com' in application.properties in the src/main/resources
Create a new controller in the src/test/java folder that mocks the thirdparty server.
Override 'api_host' for testing to 'localhost'.
Here is the code for steps 2 and 3 in one file for brevity:
#RestController
class FacebookMockController {
#RequestMapping("/oauth/access_token")
public String oauthToken() {
return "TEST_TOKEN";
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest({"api_host=localhost",})
public class TestHelloControllerIT {
#Test
public void getHelloToFacebook() throws Exception {
String url = new URL("http://localhost:8080/hello_to_facebook").toString();
RestTemplate template = new TestRestTemplate();
ResponseEntity<String> response = template.getForEntity(url, String.class);
assertThat(response.getBody(), equalTo("TEST_TOKEN_PROCESSED"));
// Assert that facebook mock got called:
// for example add flag to mock, get the mock bean, check the flag
}
}
Is there a nicer way to do this? All feedback is appreciated!
P.S. Here are some complications I encountered putting this answer into more realistic app:
Eclipse mixes test and main configuration into classpath so you might screw up your main configuration by test classes and parameters: https://issuetracker.springsource.com/browse/STS-3882 Use gradle bootRun to avoid it
You have to open access to your mocked links in the security config if you have spring security set up. To append to a security config instead of messing with a main configuration config:
#Configuration
#Order(1)
class TestWebSecurityConfig extends WebSecurityConfig {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/access_token").permitAll();
super.configure(http);
}
}
It is not straightforward to hit https links in integration tests. I end up using TestRestTemplate with custom request factory and configured SSLConnectionSocketFactory.
If you use RestTemplate inside the HelloController you would be able to test it MockRestServiceTest, like here: https://www.baeldung.com/spring-mock-rest-template#using-spring-test
In this case
#RunWith(SpringJUnit4ClassRunner.class)
// Importand we need a working environment
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestHelloControllerIT {
#Autowired
private RestTemplate restTemplate;
// Available by default in SpringBootTest env
#Autowired
private TestRestTemplate testRestTemplate;
#Value("${api_host}")
private String apiHost;
private MockRestServiceServer mockServer;
#Before
public void init(){
mockServer = MockRestServiceServer.createServer(this.restTemplate);
}
#Test
public void getHelloToFacebook() throws Exception {
mockServer.expect(ExpectedCount.manyTimes(),
requestTo(buildURI("http", this.apiHost, "/oauth/access_token"))))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body("{\"token\": \"TEST_TOKEN\"}")
);
// You can use relative URI thanks to TestRestTemplate
ResponseEntity<String> response = testRestTemplate.getForEntity("/hello_to_facebook", String.class);
// Do the test you need
}
}
Remember that you need a common RestTemplateConfiguration for autowiring, like this:
#Configuration
public class RestTemplateConfiguration {
/**
* A RestTemplate that compresses requests.
*
* #return RestTemplate
*/
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
And that you have to use it inside HelloController as well
#RestController
public class HelloController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
String response = restTemplate.getForEntity(buildURI("https", "graph.facebook.com", "/oauth/access_token"), String.class).getBody();
// .. Do something with a response
return response;
}
}
2018 Things have improved much.
I ended up using spring-cloud-contracts
Here's a video introduction https://www.youtube.com/watch?v=JEmpIDiX7LU . The first part of the talk walk you through a legacy service. That's the one you can use for external API.
Gist is,
You create a Contract for the external service using Groovy DSL or other methods that even support explicit calls/proxy or recording. Check documentation on what works for you
Since you dont actually have control over the 3rd party in this case, you will use the contract-verifier and create the stub locally but remember to skipTests
With the stub-jar now compiled and available you can run it from within your test cases as it will run a Wiremock for you.
This question and several stackoverflow answers helped me find the solution so here is my sample project for the next person who has these and other similar microservices related tests.
https://github.com/abshkd/spring-cloud-sample-games
With everything working once you will never ever look back and do all your tests with spring-cloud-contracts
#marcin-grzejszczak the author, is also on SO and he helped a lot figure this out. so if you get stuck, just post on SO.
You could have another spring configuration file that exposes the same endpoint as the HelloController class. You could then simply return the canned json response.
From your code, I'm not sure about just what you are trying to accomplish. If you simply want to see that the call to facebook works then there's no substitute for testing against the service that actually talks to facebook. Mocking the facebook response just to ensure that it is mocked correctly, doesn't strike me as a terribly useful test.
If you are testing to see that the data that comes back from facebook is mutated in some way and you want to make sure that the work being done on it is correct, then you could do that work in a separate method that took the facebook response as a paramater, and then carried out the mutation. You could then check based on various json inputs that it was working correctly.
You could test without bringing the web service into it at all.

Resources