Testing Spring - REST API without mock - java

How does one invoke methods in a rest service that is not written in spring or java (its wcf rest service) using JUnit & Spring?
Note: I want to do HTTP-GET so mocking is not the case here.
Does Spring let me use restTemplate.getForObject(..) from JUnit? Cucumber?
So far I have a client written using Spring:
#SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
private static final String SERVICE_URL="http://localhost:12345/PrivilegesService/IsAlive";
public static void main(String args[]) {
SpringApplication.run(Application.class);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
boolean response = restTemplate.getForObject(SERVICE_URL, boolean.class);
log.info("response: "+ response); // print : true
};
}
}
I want my tests look :
public class StepDefinitions {
#When("^application is up$")
public void the_client_issues_GET_version(){
}
#Then("^the server should be running$")
public void the_client_receives_status_code_of() {
boolean response = restTemplate.getForObject(SERVICE_URL, boolean.class);
AssertTrue(true,response);
}
}

RestTemplate works in Junit as well. It doesn't matter if its a source code or test code.
REST is based on HTTP, so it doesn't matter what framework is used to write a REST service. As long as it is a REST service, you should be able to call it

Related

Spring Boot: How to change the Content Security Policy at runtime?

I'm trying to hot-reload a change in the content security policy (CSP) of my Spring Boot application, i.e. the user should be able to change it via an admin UI without restarting the server.
The regular approach in Spring Boot is:
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) {
// ... lots more config here...
http.headers()
.addHeaderWriter(
StaticHeadersWriter(
"Content-Security-Policy",
"<some policy string>"
)
)
}
}
... but this doesn't allow for reconfiguration once it has been assigned.
Can I make this (re-)configurable at runtime? Reloading the application context is not an option, I need to be able to adapt only this particular setting.
Easy-Peasy, we only need to expose a (n appropriate) HeaderWriter as a bean! ContentSecurityPolicyHeaderWriter looks appropriate & sufficient for us, but we are also free to implement a custom:
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
#Bean
public ContentSecurityPolicyHeaderWriter myWriter(
#Value("${#my.policy.directive:DEFAULT_SRC_SELF_POLICY}") String initalDirectives
) {
return new ContentSecurityPolicyHeaderWriter(initalDirectives);
}
Then with:
#Autowired
private ContentSecurityPolicyHeaderWriter myHeadersWriter;
#Override
public void configure(HttpSecurity http) throws Exception {
// ... lots more config here...
http.headers()
.addHeaderWriter(myHeadersWriter);
}
..., we can change the header value with these demo controllers:
#GetMapping("/")
public String home() {
myHeadersWriter.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
return "header reset!";
}
#GetMapping("/foo")
public String foo() {
myHeadersWriter.setPolicyDirectives("FOO");
return "Hello from foo!";
}
#GetMapping("/bar")
public String bar() {
myHeadersWriter.setPolicyDirectives("BAR");
return "Hello from bar!";
}
We can test:
#SpringBootTest
#AutoConfigureMockMvc
class DemoApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void testHome() throws Exception {
this.mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("header reset!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, DEFAULT_SRC_SELF_POLICY));
}
#Test
public void testFoo() throws Exception {
this.mockMvc.perform(get("/foo"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from foo!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "FOO"));
}
#Test
public void testBar() throws Exception {
this.mockMvc.perform(get("/bar"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from bar!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "BAR"));
}
}
... also in browser:
All in one github.(sorry all in main class!:)
Refs: only this
The problem with the (my) accepted answer is:
(just for the show case, but:) We modify "singleton scope property" on (every) request!!!
When we add a "stress" test wrapper like this.
( ... wait until all threads finish their work in java ?? -> ExecutorCompletionService, since Java:1.5;)
It badly fails (header has not the "expected" value):
#Test
void testParallel() throws Exception {
// 200 cycles, with 0 (== #cpu) threads ...
final StressTester<Void> stressTestHome = new StressTester<>(Void.class, 200, 0, // ... and these (three) jobs (firing requests at our app):
() -> {
home(); // here the original tests
return null;
},
() -> {
foo(); // ... with assertions ...
return null;
},
() -> {
bar(); // ... moved to private (non Test) methods
return null;
}
);
stressTestHome.test(); // run it, collect it and:
stressTestHome.printErrors(System.out);
assertTrue(stressTestHome.getExceptionList().isEmpty());
}
As in mock as in (full) server mode... ;(;(;(
We will encounter the same problem, when we want to change that header from a "lower scope" (than singleton..so any other scope:) ;(;(;(
If we want singleton scope policy for that header, and only "trigger the reload" (for all subsequent requests), we can stop reading. (answer 1 is ok, as i actually "initially understood" the question & answered:)
But if we want that "per request header" with spring-security, we have to pass this test! :)
One possible solution: Method Injection!
So back to our custom HeaderWriter implementation:
package com.example.demo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.HeaderWriter;
// abstract!
public abstract class MyContentSecurityPolicyHeaderWriter implements HeaderWriter {
// ... no state!!!
public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
public static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
#Override // how cool, that there is a HttpServletRequest/-Response "at hand" !?!
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (!response.containsHeader(CONTENT_SECURITY_POLICY_HEADER)) {
// responsible for the header key, but for the value we ask: delegate
response.setHeader(CONTENT_SECURITY_POLICY_HEADER, policyDelegate().getPolicyDirectives());
}
}
// TLDR xDxD
protected abstract MyContentSecurityDelegate policyDelegate();
}
Thanks, again!;)
With this tiny (but managed) "context holder":
package com.example.demo;
import lombok.*;
#NoArgsConstructor
#AllArgsConstructor(staticName = "of")
public class MyContentSecurityDelegate {
#Getter
#Setter
private String policyDirectives;
}
We do this (with spring-java-config, How to create bean using #Bean in spring boot for abstract class):
#Configuration
class FreakyConfig {
#Value("${my.policy.directive:DEFAULT_SRC_SELF_POLICY}")
private String policy;
#Bean
#RequestScope // !! (that is suited for our controllers)
public MyContentSecurityDelegate delegate() {
return MyContentSecurityDelegate.of(policy);
}
#Bean
public MyContentSecurityPolicyHeaderWriter myWriter() {
return new MyContentSecurityPolicyHeaderWriter() { // anonymous inner class
#Override
protected MyContentSecurityDelegate policyDelegate() {
return delegate(); // with request scoped delegate.
}
};
}
}
..then our controllers do that (autowire & "talk" to the delegate):
#Autowired // !
private MyContentSecurityDelegate myRequestScopedDelegate;
#GetMapping("/foo")
public String foo() {
// !!
myRequestScopedDelegate.setPolicyDirectives("FOO");
return "Hello from foo!";
}
Then all tests pass! :) pushed to (same)github.
But to achieve the goal: "Write headers request (even thread) specific", we can use any other technique (matching our stack & needs, beyond spring-security):
with or without spring-boot
servlet
with spring-mvc/without
javax.servlet.*:
Any Servlet, Filter, or servlet *Listener instance that is a Spring bean is registered with the embedded container..
from Registering Servlets, Filters, and Listeners as Spring Beans
or reactive ...
Mo' Links:
How can I add a filter class in Spring Boot?
https://www.baeldung.com/spring-response-header
https://www.baeldung.com/spring-boot-add-filter
Happy Coding!

Spring Boot - Mock a POST REST request to an external API

I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");

Getting 406 Could not find acceptable representation /Spring JSON Test. How to ignore .htm extension in tests?

Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html

Spring Integration manually publish message to channel

I'm in the process of learning how to use the Java Spring Framework and started experimenting with Spring Integration. I'm trying to use Spring Integration to connect my application to an MQTT broker both to publish and subscribe to messages but I'm having trouble finding a way to manually publish messages to an outbound channel. If possible I want to build it using notations in the java code exclusively rather than xml files defining beans and other related configuration.
In every example I've seen the solution to manually publishing a message seems to be to use a MessagingGateway Interface and then use the SpringApplicationBuilder to get the ConfigurableApplicationContext to get a reference to the gateway interface in the main method. The reference is then used to publish a message. Would it be possible to use AutoWired for the interface instead? In my attempts I just get a NullPointer.
My aim is to build a game where I subscribe to a topic to get game messages and then whenever the user is ready to make the next move, publish a new message to the topic.
Update:
This is one of the examples I've been looking at of how to setup an outbound channel: https://docs.spring.io/spring-integration/reference/html/mqtt.html
Update 2 after answer from Gary Russel:
This is some example code I wrote after looking at examples which gets me a NullPointer when using #AutoWired for the Gateway when running gateway.sendToMqtt in Controller.java. What I want to achieve here is to send an mqtt message manually when a GET request is handled by the controller.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
Controller.java
#RestController
#RequestMapping("/publishMessage")
public class Controller {
#Autowired
static Gateway gateway;
#RequestMapping(method = RequestMethod.GET)
public int request(){
gateway.sendToMqtt("Test Message!");
return 0;
}
}
MqttPublisher.java
#EnableIntegration
#Configuration
public class MqttPublisher {
#Bean
public MqttPahoClientFactory mqttClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(){
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("clientPublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface Gateway {
void sendToMqtt(String data);
}
}
Update:
Not sure if this is the proper logging but it is what I get from adding:
logging.level.org.springframework.web=Debug
logging.level.org.hibernate=Error
to application.properties.
https://hastebin.com/cuvonufeco.hs
Use a Messaging Gateway or simply send a message to the channel.
EDIT
#SpringBootApplication
public class So47846492Application {
public static void main(String[] args) {
SpringApplication.run(So47846492Application.class, args).close();
}
#Bean
public ApplicationRunner runner(MyGate gate) {
return args -> {
gate.send("someTopic", "foo");
Thread.sleep(5_000);
};
}
#Bean
#ServiceActivator(inputChannel = "toMqtt")
public MqttPahoMessageHandler mqtt() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "foo",
clientFactory());
handler.setDefaultTopic("myTopic");
handler.setQosExpressionString("1");
return handler;
}
#Bean
public MqttPahoClientFactory clientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttIn() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", "bar", "someTopic");
adapter.setOutputChannelName("fromMqtt");
return adapter;
}
#ServiceActivator(inputChannel = "fromMqtt")
public void in(String in) {
System.out.println(in);
}
#MessagingGateway(defaultRequestChannel = "toMqtt")
public interface MyGate {
void send(#Header(MqttHeaders.TOPIC) String topic, String out);
}
}

Spring boot testing of a rest client using #RestClientTest

I am using spring boot 1.5.8 and want to test my client:
#Component
public class RestClientBean implements RestClient {
private Map<String, RestTemplate> restTemplates = new HashMap<>();
#Autowired
public RestClientBean(RestTemplateBuilder builder, SomeConfig conf) {
restTemplates.put("first", builder.rootUri("first").build();
restTemplates.put("second", builder.rootUri("second").build();
}
}
with the following test:
#RunWith(SpringRunner.class)
#RestClientTest(RestClient.class)
public class RestClientTest {
#Autowired
private RestClient client;
#Autowired
private MockRestServiceServer server;
#TestConfiguration
static class SomeConfigFooBarBuzz {
#Bean
public SomeConfig provideConfig() {
return new SomeConfig(); // btw. not sure why this works,
// but this is the only way
// I got rid of the "unable to load
// SomeConfig auto-wire" or something like this :)
// Anyway not my main issue
// EDIT: just realized that the whole
// #TestConfiguration part can be avoided by
// adding SomeConfig.class to the classes in the
// #RestClientTest annotation
}
}
#Before
public void setUp() throws Exception {
server.expect(requestTo("/somePath")) // here an exception is thrown
// (main issue)
.andRespond(withSuccess("<valid json>", MediaType.APPLICATION_JSON));
}
}
The exception is very clear:
java.lang.IllegalStateException: Unable to use auto-configured
MockRestServiceServer since MockServerRestTemplateCustomizer has been bound to
more than one RestTemplate
But is it somehow possible to get this tested or is it not allowed to instantiate two different rest templates in one client class?
I have just the need to use the first rest template in some cases and the second one in some others.
After some days of investigation and communication with spring folks via GitHub I found a solution for me and not receiving an answer here means my solution might be valuable for someone:
#RunWith(SpringRunner.class)
#RestClientTest(RestClient.class)
public class RestClientTest {
#Autowired
private RestClient client;
private MockRestServiceServer firstServer;
private MockRestServiceServer secondServer;
private static MockServerRestTemplateCustomizer customizer;
#TestConfiguration
static class RestTemplateBuilderProvider {
#Bean
public RestTemplateBuilder provideBuilder() {
customizer = new MockServerRestTemplateCustomizer();
return new RestTemplateBuilder(customizer);
}
}
#Before
public void setUp() {
Map<RestTemplate, MockRestServiceServer> servers = customizer.getServers();
// iterate and assign the mock servers according to your own strategy logic
}
#Test
public void someTest() {
firstServer.expect(requestTo("/somePath"))
.andRespond(withSuccess("some json body"),
MediaType.APPLICATION_JSON));
// call client
// verify response
}
Basically specify a number of mock servers same as the number of rest templates you use in your client code, then specify a test configuration providing a rest builder with a customizer, so that your client code's rest templates will be built via this customized builder. Then use the customizer to get the mock servers bounded to the created rest templates and define expectations on them as you want.

Categories

Resources