I'm trying to mock org.springframework.web.client.RestOperations.exchange in Spock.
Spock fails with
Too few invocations for:
1 * restOperations.exchange("https://test.com", HttpMethod.POST, _ as HttpEntity, String) (0 invocations)
Unmatched invocations (ordered by similarity):
1 * restOperations.exchange('https://test.com', POST, <whatever,[]>, class java.lang.String, [])
I think the problem is related to the fact that the exchange method is overloaded and the version I'm trying to call has vararg arguments.
How do I define this interaction, so the test succeeds?
MySubject.java:
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestOperations;
public class MySubject {
private final RestOperations rest;
public MySubject(RestOperations rest) {
this.rest = rest;
}
public void doStuff() {
HttpEntity<String> httpEntity = new HttpEntity<>("whatever");
rest.exchange("https://test.com", HttpMethod.POST, httpEntity);
}
}
MyTest.groovy:
import org.apache.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.web.client.RestOperations
import spock.lang.Specification
class MyTest extends Specification {
RestOperations restOperations = Mock(RestOperations)
MySubject subject = new MySubject(restOperations)
def "test"() {
when:
subject.doStuff()
then:
1 * restOperations.exchange("https://test.com", HttpMethod.POST, _ as HttpEntity, String)
}
}
You have multiple problems:
In the application you import org.springframework.http.HttpEntity, in the test org.apache.http.HttpEntity. You need to correct that.
The call rest.exchange("https://test.com", HttpMethod.POST, httpEntity); in your application does not even compile because there is no such signature in the RestOperations class. You need to add the parameter String.class.
In the test you need to reflect the method signature including the varargs, i.e. the real method signature has 5 arguments.
If you fix all of these your test runs smoothly:
package de.scrum_master.stackoverflow.q61135628;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestOperations;
public class MySubject {
private final RestOperations rest;
public MySubject(RestOperations rest) {
this.rest = rest;
}
public void doStuff() {
HttpEntity<String> httpEntity = new HttpEntity<>("whatever");
rest.exchange("https://test.com", HttpMethod.POST, httpEntity, String.class);
}
}
package de.scrum_master.stackoverflow.q61135628
import org.springframework.http.HttpMethod
import org.springframework.web.client.RestOperations
import spock.lang.Specification
class MyTest extends Specification {
RestOperations restOperations = Mock()
MySubject subject = new MySubject(restOperations)
def "test"() {
when:
subject.doStuff()
then:
1 * restOperations.exchange("https://test.com", HttpMethod.POST, _, String, _)
// Or if you want to be more specific:
// 1 * restOperations.exchange("https://test.com", HttpMethod.POST, _, String, [])
}
}
Related
I am trying to call asynchronous method that returns Future Object, I suppose that it will print YYY and then XXX since the XXX is in a method that is 1 sec long. however, after deploying the code, it did not work properly, I tried the same thing with 10 objects and they printed sequentially. where is the error
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package testfuture;
import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.ejb.Asynchronous;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
#Singleton
#TransactionManagement(TransactionManagementType.BEAN)
public class TestFuture {
#Schedule(minute = "*/1", hour = "*", persistent = false)
public void start() {
try{
Future<String> r = inparallelMethod(5) ;
System.out.print("YYY");
r.get();
}
catch ( InterruptedException ie )
{
System.out.print(ie.getMessage());
}
catch (ExecutionException e)
{
System.out.print(e.getMessage());
}
}
#Asynchronous
public Future<String> inparallelMethod(int i) throws InterruptedException
{
Thread.sleep(1000);
System.out.print("XXX");
return null;
}
}
Because you call inparallelMethod inside the instanced class "bybassing" container managmenet of calling an async menthod.
You have to define the async method in another bean, #Inject that bean and call the method.
#Singleton
#TransactionManagement(TransactionManagementType.BEAN)
public class TestFuture {
#Inject
AsyncService service;
#Schedule(minute = "*/1", hour = "*", persistent = false)
public void start() {
try{
Future<String> r = service.inparallelMethod(5) ;
System.out.print("YYY");
r.get();
}
catch ( InterruptedException ie )
{
System.out.print(ie.getMessage());
}
catch (ExecutionException e)
{
System.out.print(e.getMessage());
}
}
}
#Stateless
public class AsyncService {
#Asynchronous
public Future<String> inparallelMethod(int i) throws InterruptedException
{
Thread.sleep(1000);
System.out.print("XXX");
return null;
}
}
I post the code to make work the async but your code in very poor to test an async scenario, Thread.sleep inside a java-ee is a very bad practice because thread are managed by container and you can't know wich thread you are really sleeping!
I have the following function from Spring Boot. I cannot do it with declarative client thus my uri domain changed after every call so i need a RestTemplate like in Spring Boot.
How can i achieve the same in Micronaut?
private static void getEmployees()
{
final String uri = "http://localhost:8080/springrestexample/employees.xml";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
Something like this is a good starting point...
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import javax.inject.Inject;
#Controller("/")
public class SomeController {
// The url does not have to be
// hardcoded here. Could be
// something like
// #Client("${some.config.setting}")
#Client("http://localhost:8080")
#Inject
RxHttpClient httpClient;
#Get("/someuri")
public HttpResponse someMethod() {
String result = httpClient.toBlocking().retrieve("/springrestexample/employees.xml");
System.out.println(result);
// ...
return HttpResponse.ok();
}
}
I hope that helps.
EDIT
Another similar approach:
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
#Controller("/")
public class SomeController {
private final RxHttpClient httpClient;
public SomeController(#Client("http://localhost:8080") RxHttpClient httpClient) {
this.httpClient = httpClient;
}
#Get("/someuri")
public HttpResponse someMethod() {
String result = httpClient.toBlocking().retrieve("/springrestexample/employees.xml");
System.out.println(result);
// ...
return HttpResponse.ok();
}
}
What is recommended/best way to validate the post request DTO bean ?
If validation failed I need to send customized error message like
{
"code": "invalid_fields",
"fields": {
"email": "Required",
"password": "Required",
}
}
DTO model
public class SignUpRequest {
#JsonProperty("email")
String email;
#JsonProperty("password")
String password;
public Result validate(){
}
}
controller
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
SignUpRequest DTO has the method validate.
What is the spring way of doing the validation ?
Thanks.
You can use the following technique.
add the following dependencies in your gradle/maven file
compile "javax.validation:validation-api:2.0.1.Final"
compile "org.hibernate.validator:hibernate-validator:6.0.9.Final"
Hibernate-validator is implementation of validation-api 2.0
Add Validated annotation to your controller class
import org.springframework.validation.annotation.Validated;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
}
Add Valid annotation to your method parameter
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
Add Validated annotation to your dto class
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Email;
#Validated
public class SignUpRequest {
#JsonProperty("email")
#Email
String email;
#JsonProperty("password")
#NotNull
String password;
}
Add ExceptionTranslator with RestControllerAdvice annotation
#RestControllerAdvice
public class ExceptionTranslator {
/**
* Exception handler for validation errors caused by method parameters #RequesParam, #PathVariable, #RequestHeader annotated with javax.validation constraints.
*/
#ExceptionHandler
protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException exception) {
List<ApiError> apiErrors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
String value = (violation.getInvalidValue() == null ? null : violation.getInvalidValue().toString());
apiErrors.add(new ApiError(violation.getPropertyPath().toString(), value, violation.getMessage()));
}
return ResponseEntity.badRequest().body(apiErrors);
}
}
Create ApiError class
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ApiError {
#JsonIgnore
private int code;
private String field;
private String value;
private String message;
public ApiError(String message) {
this.message = message;
}
public ApiError(String field, String value, String message) {
this.field = field;
this.value = value;
this.message = message;
}
}
Now if password field is missed you'll see the following response structure:
[
{
"field": "password",
"message": "must be filled"
}
]
If you would like to use some custom logic to validate your fields you may use the following approach
Create specific annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Constraint(validatedBy = ContactRequiredParametersValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface ContactRequiredParameters {
String message() default
"Email or phone must be filled";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Create custom validator
import org.apache.commons.lang.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ContactRequiredParametersValidator implements ConstraintValidator<ContactRequiredParameters, Object[]> {
#Override
public boolean isValid(Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null) {
return true;
}
if (!(value[0] instanceof SignUpRequest)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
SignUpRequest contact = (SignUpRequest) value[0];
return StringUtils.isNotEmpty(contact.getPassword());
}
}
add #ContactRequiredParameters annotation to your method in controller
#PostMapping(value = "/register")
#ContactRequiredParameters
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest)
That's all. Hope it helps
Spring boot supports validation out of the box using validation-api which is included with spring web mvc starter:
#RestController
#RequiredArgsConstructor
public class TestController {
#PutMapping(value = "/", consumes = APPLICATION_JSON_VALUE)
#ResponseStatus(NO_CONTENT)
public void test(#Valid #RequestBody final SignUpRequest params) {
...
}
}
You can annotate your SignUpRequest using annotations such as javax.validation.constraints.NotNull and other more complex ones.
the error messages can be customised with message properties or hard coded strings if i18n/l10n is of less interest to you.
Sample here: https://spring.io/guides/gs/validating-form-input/
If you want behaviour outside of the provided annotations you can write a custom annotation that can do that, e.g.
#Target({FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = NotPastValidator.class)
#Documented
public #interface NotPast {
String message() default "date must not be in the past";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then:
public class NotPastValidator implements ConstraintValidator<NotPast, LocalDate> {
#Override
public void initialize(final NotPast constraintAnnotation) {
// nothing to do.
}
#Override
public boolean isValid(final LocalDate value, final ConstraintValidatorContext context) {
// As the Bean Validation specification recommends, we consider null values as being valid.
return value == null || isDateNotPast(value);
}
private boolean isDateNotPast(final LocalDate value) {
return ...
}
}
And finally just annotate your field:
#NotPast
Of course this is just an example with some code I previously used, you'll need to adapt to your needs.
If you don't want to use the validator API at all you can equally just write your own code to programatically check and throw some type of custom exception when invalid. This can then be caught in the controller and you can send what ever response you want, e.g.
#RestController
public class PaymentController {
#PostMapping(value ="/", consumes = APPLICATION_JSON_VALUE)
public void makePayment(#RequestBody final PaymentParams params) {
// validationService.validate(params);
}
#ExceptionHandler(MyValidationException.class)
public ResponseEntity<ExceptionDto> paymentCardException(final MyValidationException e) {
return status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(new ExceptionDto(e.getMessage));
}
}
I'd say given the validation API is well supported by spring, to me it makes sense to apply declarative validations where possible when using this stack. Custom rules can be a little painful, but you can use a multi faceted approach with some annotation based and equally you can perform some more complex validations in your own service.
This is a custom validation.
#PostMapping
private ResponseEntity<?> addMessage(#RequestBody Message message) {
Map<String, String> response = new HashMap<>();
if (message.getInputMessage() == null || message.getInputMessage().equals("")) {
response.put("status", "E");
response.put("message", "input message can not be empty");
return ResponseEntity.ok(response);
}
int id = messageService.addMessage(message);
if (id <= 0) {
response.put("status", "E");
response.put("message", "add message has error");
return ResponseEntity.ok(response);
}
response.put("status", "S");
response.put("message", "success");
return ResponseEntity.ok(response);
}
I want to validate the trimmed string length, like:
#Size(min = 5, max = 20, message = "Please enter a valid username (5-20 characters)")
String userName;
Given user name foo (6 chars), it will be trimmed to foo (3 chars) in the setter method, however, validate against the raw input seems a bit of useless.
Instead of altering the validation procedure, you could write your own constraint which wraps the expected validation logic.
I have found such an implementation of a #TrimmedSize annotation at the auto-trader-spring-spike project:
package com.autotrader.frameworks;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
#Documented
#Constraint(validatedBy = TrimmedSizeValidator.class)
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface TrimmedSize {
String message() default "size too small";
Class<?>[] groups() default {};
int min() default 0;
Class<? extends Payload>[] payload() default {};
}
And the validator:
package com.autotrader.frameworks;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class TrimmedSizeValidator implements ConstraintValidator<TrimmedSize, String> {
private int length;
public void initialize(TrimmedSize trimmedSize) {
length = trimmedSize.min();
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null || value.length() == 0) {
return false;
}
return value.trim().length() >= length;
}
}
It uses an Apache License 2.0, but even then you could write this code from scratch once you've seen it.
Any one know how does Async.asyncHandler() work and if Async.processOnEvent() can only used in [Before] method.(Any one know some helpful document besides http://docs.flexunit.org/).
I define a MXML component named HelloCompo(extends Vbox), and the component define a function named hello(), in the hello() dispacthed a customer event named HelloEvent(the event type just named "hello"), and in another function named init() listened for the event, I want to test whether the event is dispatched properly or not. So I have the test following:
var helloCompo = new HelloCompo();
helloCompo.hello();
helloCompo.addEventListener("hello", Async.asyncHandler(this, handleHello, 1000, null, handleTimeOut));
The test will always excute the handleTimeOut method(means the HelloEvent is not dispatched, but when helloCompo.hello() excute, it really dispacthed, so what's going wrong?)
package flexUnitTests
{
import flash.events.Event;
import org.flexunit.asserts.assertTrue;
import org.flexunit.asserts.fail;
import org.flexunit.async.Async;
public class HelloTest
{
private var helloCompo:HelloCompo;
[Before]
public function setUp():void
{
helloCompo = new HelloCompo();
}
[After]
public function tearDown():void
{
helloCompo = null;
}
[Test(async)]
public function testHello():void
{
var handler:Function = Async.asyncHandler(this, helloHandler, 300, null, helloFailed);
helloCompo.addEventListener("hello", handler);
helloCompo.hello();
}
private function helloHandler(event:Event, passThroughObject:Object):void
{
//assert somthing
}
private function helloFailed(event:Event, passThroughObject:Object):void
{
fail("hello not dispatched");
}
}
}
HelloCompo.as
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
public class HelloCompo extends EventDispatcher
{
public function HelloCompo(target:IEventDispatcher=null)
{
super(target);
}
public function hello():void
{
dispatchEvent(new Event("hello"));
}
}
}
I think you need to add your event listener before calling hello() actually