How to Unit Test Spring MVC validation annotation error messages? - java

Let's say I have a model class like this:
public class User {
#NotEmpty(message = "Email is required")
private String email;
}
I want to be able to unit test for the custom error message "Email is required"
So far, I have this Unit Test code which works, but does not check for the error message.
#Test
public void should_respond_bad_request_with_errors_when_invalid() throws Exception
{
mock().perform(post("/registration/new"))
.andExpect(status().isBadRequest())
.andExpect(view().name("registration-form"))
.andExpect(model().attributeHasFieldErrors("user", "email"));
}

Seems you can't.
But I suggest you work around through the attributeHasFieldErrorCode method.
Having the following:
#NotNull(message="{field.notnull}")
#Size(min=3, max=3, message="{field.size}")
public String getId() {
return id;
}
I have in my test methods the following (in case my data fails for Minimum or Maximum constraints, it belongs to the #Size annotation)
.andExpect(model().attributeHasFieldErrorCode("persona", "id", is("Size")))
or with
.andExpect(model().attributeHasFieldErrorCode("persona", "id", "Size"))
The method has two versions, with and without Hamcrest
Even when you use message = "Email is required" (raw/direct content) and I use message="{field.size}") the key to be used from a .properties file, our message attribute content belongs practically for a unique kind of annotation. In your case for a #NotNull and in my case for a Size.
Therefore, in some way does not matter what is the message (raw or key), the important is that the owner of that message (in this case the annotation) did its work and it has been validated through the attributeHasFieldErrorCode method.
Do not confuse with the similar methods names: attributeHasFieldErrors and attributeHasFieldErrorCode.

I have had the same issue. I have found some solution described below. The main idea is to check error code, not the error message.
BindingResult bindingResult = (BindingResult)
mock().perform(post("/registration/new")).andReturn().getModelAndView().getModelMap().get("org.springframework.validation.BindingResult.user");
assertEquals(bindingResult.getFieldError(email).getCode(), "error.code");

Related

Validating with Spring that a Boolean element comes explicitly defined

We got this REST endpoint in which one of the field is mapped to a Boolean (The wrapper class) instance. We are using a Boolean instead of a boolean because design decision, so this is non-negotiable.
This Boolean value is mandatory and it must be specified by the sender ("whateverValue":"" should return a 400 error), but when arriving to the endpoint, the value is automatically converted to a correct false value.
So, the question is: Can this be done? Are we not understanding the contract of using a "Boolean" object instead of the primitive?
EDIT: Just to clarify, we are already validating "whateverValue":null, and the value can be either true or false, so, as far as I know, neither #NotNull or #AssertTrue/False can be used here.
If you want to validate the Object Type Boolean you should use #NotNull
Here is a question where this has been asked before.
I use #NotNull if a boolean is mendatory to be set ans #AssertTrue/false to verify the incoming value has a specific state.
Hope this helps
I coded your scenario as follows and it was ok!
Please correct me if my understanding (from your scenario) is incorrect.
Create DTO
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class TestDto {
#NotNull
Boolean testValue;
//getter and setter
}
Create Endpoint
#RestController
public class testController {
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void accountTrades(#RequestBody #Valid TestDto testDto) {
System.out.println(testDto.getTestValue());
}
}
Using Postman, when I send testValue:"", it throws a notnull exception, that means the server has received null, and there is no null to false conversion. (It worked correctly!)
Server response is as follows

#StringDef lint doesn't seem to work

I am creating a custom annotation for firebase events. My inspiration comes from official android docs and other #StringDef annotations. It looks like this:
#Retention(SOURCE)
#StringDef({
EVENT_1, EVENT_2
})
#Size(max = 32) // firebase limit
public #interface UserActivityEvents {
String EVENT_1 = "ev_1";
String EVENT_2 = "really_long_event_name_that_exceeds_32_chars_limit";
...
}
The class that encapsulates the logging is defined as follows:
public class UserActivityEvent {
private #UserActivityEvents String eventName;
private UserActivityEvent() {
}
public UserActivityEvent(#UserActivityEvents String eventName) {
this.eventName = eventName;
}
...
}
The problem is that the AS lint is never triggered, no matter what I specify in the constructor, even if I do something like this:
new UserActivityEvent("asd");
My expectations for #UserActivityEvents are:
The parameter in constructor of UserActivityEvent can only be one of the strings defined in #StringDef
Event length doesn't exceed the 32 chars limit
If one of these rules are violated, the code is underlined (classic AS error highlight) and the error message is displayed when hovering over the code.
Edit: I found out that if I define a new event EVENT_3 in UserActivityEvents but don't include it in #UserActivityEvents and I try to reference it as new UserActivityEvent(UserActivityEvents.EVENT_3), the lint is properly triggered and says this: "Must be one of: EVENT_1, EVENT_2 or length must be at most 32". In case of using new UserActivityEvent("asd") it doesn't work

Getting default message of Javax.Validation.Constraints from within a subclass?

I have an HTML form that is submitting data to a spring post method. I've enabled validation with the javax.validation library and the #Valid tag:
public String openForm(#ModelAttribute("task")#Valid Task task,
BindingResult result, Model model) {
if(result.hasErrors()) {
System.out.println("Form does not validate.");
List<ObjectError> errors = result.getAllErrors();
for(ObjectError error: errors) {
System.out.println(error.getDefaultMessage());
}
}
return "home";
}
Task is the main object being binded to the form, but Task contains a class called User. User has a fullName. There are two fields that I am trying to validate:
systemType is a String within the Task object:
#Size(min=1, message="The System Type must contain a valid option")
private String systemType;
fullName is a String within the User object:
#Size(min=1, message="A User must be selected.")
private String fullName;
The validation itself is working, but what I am trying to figure out how to do is get the default message working. When I iterate through the BindingResult result for errors and print out the error.getDefaultMessage() the systemType error message works as expected.
However, when there is a validation error with the fullName field, it prints:
Property 'user.fullName' threw exception; nested exception is java.lang.ArrayIndexOutOfBoundsException: 1
I believe this to be because it is an inner class of the main class Task which is what is actually being validated. I guess I could just write some logic to say that if this error comes along to just print the error message I actually want - but is there a way to go within error and get the default message I originally specified?

javax.validation How get property name in a validation message

I'm looking for a proper way to use property name inside validation messages, like {min} or {regexp}.
I've googled this question a few times now and apparently there isn't a native method for doing this.
#NotNull(message = "The property {propertyName} may not be null.")
private String property;
Has anyone experienced this before and has managed to find a solution for this?
UPDATE 1
Using a custom message interpolator should be something like:
public class CustomMessageInterpolator implements MessageInterpolator {
#Override
public String interpolate(String templateString, Context cntxt) {
return templateString.replace("{propertyName}", getPropertyName(cntxt));
}
#Override
public String interpolate(String templateString, Context cntxt, Locale locale) {
return templateString.replace("{propertyName}", getPropertyName(cntxt));
}
private String getPropertyName(Context cntxt) {
//TODO:
return "";
}
}
One solution is to use two messages and sandwich your property name between them:
#NotBlank(message = "{error.notblank.part1of2}Address Line 1{error.notblank.part2of2}")
private String addressLineOne;
Then in your message resource file:
error.notblank.part1of2=The following field must be supplied: '
error.notblank.part2of2='. Correct and resubmit.
When validation fails, it produces the message "The following field must be supplied: 'Address Line 1'. Correct and resubmit."
As another workaround, you may also pass the property name to the message text as shown below:
Add the validation message to the properties file (e.g. text.properties):
javax.validation.constraints.NotNull.message=may not be null.
Then define the annotation in the code as shown below:
#NotNull(message = "propertyName {javax.validation.constraints.NotNull.message}")
private String property;

My custom accessor is never used and only the field default value is used by hibernate validator instead

I have the following code inside a javabean:
#AssertTrue
private boolean addressReferenceValid;
public boolean isAddressReferenceValid() {
if (addressType.equals(AddressType.ON_THE_FLY_ADDRESS) && StringUtils.isEmpty(addressReference)) {
return false;
}
return true;
}
The issue I have is that the isAddressReferenceValid accessor is never used and it seems that only the default value for addressReferenceValid is used (i.e. false).
I have double checked that Spring Roo did not generate its own accessor for that variable.
Can anyone please help?
P.S. I can't put the #AssertTrue annotation directly on the method because then the following key in ValidationMessages.properties file would not be resolved: AssertTrue.familyAdvertisementSearchCriteriaInfo.addressReferenceValid
edit 1:
Full bean:
#RooEquals
#RooJavaBean
public class FamilyAdvertisementSearchCriteriaInfo {
#Valid
private FamilyAdvertisementSearchCriteria searchCriteria;
private Address currentAddress;
private String addressReference;
#NotNull
private AddressType addressType;
#AssertTrue(groups=Default.class)
private boolean addressReferenceValid;
public boolean isAddressReferenceValid() {
if (addressType.equals(AddressType.ON_THE_FLY_ADDRESS) && StringUtils.isEmpty(addressReference)) {
return false;
}
return true;
}
}
Validation occurs in the following controller:
#RequestMapping(value = "/familyAdvertisementSearch", method = RequestMethod.POST, produces = "text/html")
public String familyAdvertisementSearchResults(#ModelAttribute #Validated(Default.class) FamilyAdvertisementSearchCriteriaInfo familyAdvertisementSearchCriteriaInfo, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
populateModel(model);
familyAdvertisementSearchCriteriaInfo.setCurrentAddress(memberService.retrieveCurrentMemberAddress());
return "search/familyAdvertisementSearchForm";
}
...
I think I understand now what you are trying to do. You want to place the constraint on the field, but during validation you expect the method isAddressReferenceValid to be called/used. That's not going to work. If you place a constraint on a field access is used to get the property to validate (using reflection). If you place it on a method/getter method access is used. So he placement of the annotation matters. As you already seem to have discovered placing the annotation on the method works. Of course this leads to inconsistent placement of annotations. You could:
Just place the annotation for this single constraint
switch to method annotations completely
update the Boolean every time the address type changes (and get rid of isAddressReferenceType method)
create a custom constraint for verifying the addressReference
Just some ideas. It all depends on your use case and personal preferences.

Categories