RestTemplate
is often used for consuming RESTful web services from Spring applications. It’s little known however, how to use it for more advanced use cases, that is, when you need to combine POSTing form data along with a JSON-serialized data. In this guide we’ll explore the key points of being able to do that, I’ll also show you how to unit test your REST client.
I’ve recently needed to develop Java client library for fetching some data from
SuiteCRM. Although its documentation said it provided a
REST API,
after looking at it, it was obvious that the usual way I was accustomed to using RestTemplate
wouldn’t work here.
The method login which you need to call in order to obtain a session ID from SuiteCRM expects to be called according to the following example.
<?php
...
$curl_request = curl_init();
curl_setopt($curl_request, CURLOPT_URL, $url);
...
$parameters = array(
"user_auth" => array(
"user_name" => $username,
"password" => md5($password),
"version" => "1"
),
"application_name" => "RestTest",
"name_value_list" => array(),
);
$jsonEncodedData = json_encode($parameters);
$post = array(
"method" => 'login',
"input_type" => "JSON",
"response_type" => "JSON",
"rest_data" => $jsonEncodedData
);
curl_setopt($curl_request, CURLOPT_POSTFIELDS, $post);
$result = curl_exec($curl_request);
...
?>
Some parts of the test PHP script was left out for brevity and for focusing on that fact that SuiteCRM’s REST API required a combination of posting form and JSON data. You can get the full working example here.
Much of the actual implementation has been also omitted here, as I’d like to show you the most important aspects of the solution.
public final class SuiteCRMServiceImpl implements SuiteCRMService {
private final URI suiteCRMUri;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private RestOperations restTemplate;
...
@Override
public String login(UserAuth userAuth) {
LoginRequest loginRequest = LoginRequest.builder().userAuth(userAuth).applicationName(APPLICATION_NAME).build();
UserEntry userEntry = doPost(loginRequest, UserEntry.class);
return userEntry.getId();
}
protected <R> R doPost(AbstractRequest request, Class<R> responseType) {
ResponseEntity<R> responseEntity = null;
try {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("method", request.getMethod());
formData.add("input_type", "JSON");
formData.add("response_type", "JSON");
String restData = objectMapper.writeValueAsString(request);
formData.add("rest_data", restData);
RequestEntity requestEntity = RequestEntity.post(suiteCRMUri)
.contentType(APPLICATION_FORM_URLENCODED)
.body(formData);
responseEntity = new RestTemplate().postForEntity(suiteCRMUri, requestEntity, responseType);
} catch (Exception e) {
throw new SuiteCRMServiceException(e);
}
HttpStatus statusCode = responseEntity.getStatusCode();
if (HttpStatus.OK.equals(statusCode)) {
return responseEntity.getBody();
}
throw new SuiteCRMServiceException(statusCode.getReasonPhrase());
}
...
}
In order to be able to POST form data we need to ensure two important points.
MultiValueMap
.
If you take a closer look at how FormHttpMessageConverter
works, it’s apparent that it was meant to work with MultiValueMap
's only.APPLICATION_FORM_URLENCODED
or MULTIPART_FORM_DATA
,
because FormHttpMessageConverter
supports only these media types.As the whole solution need the presence of that remote service it relies on, it’s impractical if we just leave the actual calls which goes over the wire in automated test cases.
Fortunately the functionality of RestTeample
was abstracted out to interface RestOperations
.
Its sole purpose is to help implementors mock RestTeample
out in unit test.
As you can see in the code above, RestTeample
isn’t created for every request, but it’s rather auto wired at, so that
it could be also mocked.
import static org.mockito.Matchers.*
import static org.mockito.Mockito.*
class SuiteCRMServiceUnitTest {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
RestOperations restTemplate
SuiteCRMService suiteCRMService
@Before
void before() {
restTemplate = mock(RestOperations)
when(restTemplate.postForEntity(any(URI), anyObject(), argThat(new ClassOrSubclassMatcher<UserEntry>(UserEntry)))).thenReturn(ResponseEntity.ok(new UserEntry("1234")))
...
suiteCRMService = new SuiteCRMServiceImpl("http://localhost", OBJECT_MAPPER, restTemplate)
}
@Test
void testLogin() {
...
}
...
}
Spring’s RestTeample
is very flexible in providing various way of customizing HTTP requests and processing the
response. Hardy is there anything you couldn’t do with it.
If you like Java and Spring as much as I do, sign up for my newsletter.