Assert multiple field error codes from Validation using MockMvc - spring-boot

I am trying to assert two errors due to two given constraints to my form. My form has two constraints on its single field:
#Data
#NoArgsConstructor
#NotExistingGroup(groups = SecondGroupValidation.class)
public class GroupForm {
#NotBlank(groups = FirstGroupValidation.class)
#Size(min = 2, max = 30, groups = FirstGroupValidation.class)
private String name;
}
With the following test, I want to trigger both the #NotBlank and #Size validation and assert both raised errors:
#Test
void givenGroupEmptyName_groupPost_assertErrors() throws Exception {
mvc.perform(post("/groups/add").param("name", ""))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("groups-add"))
.andExpect(model().hasErrors())
.andExpect(model().attributeErrorCount("groupForm", 2))
.andExpect(model().attributeHasFieldErrorCode("groupForm", "name", "NotBlank"))
.andExpect(model().attributeHasFieldErrorCode("groupForm", "name", "Size"));
}
The mvc doPrint() method shows both errors are given
ModelAndView:
View name = groups-add
View = null
Attribute = groupForm
value = GroupForm(name=)
errors = [Field error in object 'groupForm' on field 'name': rejected value []; codes [NotBlank.groupForm.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [groupForm.name,name]; arguments []; default message [name]]; default message [must not be blank], Field error in object 'groupForm' on field 'name': rejected value []; codes [Size.groupForm.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [groupForm.name,name]; arguments []; default message [name],30,2]; default message [size must be between 2 and 30]]
However, the test breaks with the following error
java.lang.AssertionError: Field error code expected:<Size> but was:<NotBlank>

Related

MethodArgumentNotValidException not thrown in case of validation fail

I'm trying to implement validation in Spring REST by following this tutorial. Though, my code is in Koltin unlike the tutorial.
My code is as follows -
Entity class
#Entity
class PodcastEntity(#Id #GeneratedValue(strategy = GenerationType.AUTO) #NotNull
var id: Long = 0,
#field:NotEmpty(message = "Please provide an author")
var author: String,
#field:NotEmpty(message = "Please provide a title")
var title: String,
#field:NotEmpty(message = "Please provide a description")
var description: String,
#field:NotEmpty(message = "Please provide category one")
var categoryOne: String,
#field:NotEmpty(message = "Please provide category two")
var categoryTwo: String,
var filePath: String = "")
My post method is like this in the controller -
#PostMapping("details")
fun addPodcast(#Valid #RequestBody podcastEntity: PodcastEntity) {
podcastService.addPodcast(podcastEntity)
}
My POST request in postman is like this -
{
"author" : "me 3",
"title" : "File three",
"description" : "this is a test desc"
}
Since categoryOne and categoryTwo are missing and I have not handled the exception on my own, my console should show MethodArgumentNotValidException according to the tutorial. However, I'm getting no such exception. What I'm getting is a HttpMessageNotReadableException exception -
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class com.krtkush.test.entities.PodcastEntity] value failed for JSON property categoryOne due to missing (therefore NULL) value for creator parameter categoryOne which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.krtkush.test.entities.PodcastEntity] value failed for JSON property categoryOne due to missing (therefore NULL) value for creator parameter categoryOne which is a non-nullable type at [Source: (PushbackInputStream); line: 5, column: 1] (through reference chain: com.krtkush.test.entities.PodcastEntity["categoryOne"])]
I'm unable to understand where I'm going wrong. Some help please?
You can handle this issue by providing HttpMessageNotReadableException handler
and then checking if the main cause is MissingKotlinParameterException.
After that, you can provide custom validation error.
#ExceptionHandler
override fun handleMessageNotReadableException(
exception: HttpMessageNotReadableException,
request: NativeWebRequest
): ResponseEntity<Problem> {
// workaround
val cause = exception.cause
if (cause is MissingKotlinParameterException) {
val violations = setOf(createMissingKotlinParameterViolation(cause))
return newConstraintViolationProblem(exception, violations, request)
}
return create(Status.BAD_REQUEST, UnableToReadInputMessageProblem(), request)
}
private fun createMissingKotlinParameterViolation(cause: MissingKotlinParameterException): Violation {
val name = cause.path.fold("") { jsonPath, ref ->
val suffix = when {
ref.index > -1 -> "[${ref.index}]"
else -> ".${ref.fieldName}"
}
(jsonPath + suffix).removePrefix(".")
}
return Violation(name, "must not be null")
}
This way you get get nice output with proper constraint error.
You may try to declare #ExceptionHandler for MissingKotlinParameterException directly.
Answer based on question Spring not null validation throwing HttpMessageNotReadableException instead of MethodArgumentNotValidException in kotlin
Following Damian's SO link in his answer, I found the first answer really helpful and more appropriate. I modified the #Entitiy class by making the required fields nullable (?) like this -
#Entity
class PodcastEntity(#Id #GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = 0,
#field:NotEmpty(message = "Please provide an author")
var author: String?,
#field:NotEmpty(message = "Please provide a title")
var title: String?,
#field:NotEmpty(message = "Please provide a description")
var description: String?,
#field:NotEmpty(message = "Please provide category one")
var categoryOne: String?,
#field:NotEmpty(message = "Please provide category two")
var categoryTwo: String?,
var filePath: String = "")
This makes sure that the code throws MethodArgumentNotValidException in all three cases - 1. Empty argument 2. null argument 3. Missing argument

#PathVariable of GetMapping in Spring throws an error when the input is #

I have made an autosuggest input field that automatically searches the database on every keypress. It works fine when i insert regular characters like letters and numbers but it gets spooky when you try start the search request with the character #. Doing that throws the error org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception is java.lang.NumberFormatException: For input string: "get"
When i add some letters before the # (for example des#) it will throw an 404 page not found error and if i use the % character it will throw an 400 'unauthorized' error.
This strange behavior has probably something to do that i'm expecting a GetRequest instead of a PostRequest. If i turn it into a PostMapping i'm sure the errors will dissapear. But my question is; why is this happening? Does # have a special meaning? Why does spring seemingly try to convert # to a long value even though the pathvariable is typed as String? And why has the input string become "get" according to the error? I know that in an url # has a special meaning in that it signifies an href anchor but why should it be a special character for spring?
Heres the code of my getMapping
#GetMapping("/get/varietynames/{searchString}/{languageCode}")
public List<CropVarietyNameSelectionDTO> getCropVarietySelectionDTOBySearchString(#PathVariable("searchString") #NotBlank #Pattern(regexp = "^[A-Za-z0-9]+$", message = "Search input only allows for letters and numbers")
#Size(min = 1, max = 40, message = "Search input cannot exceed 40 characters") String searchString, #PathVariable("languageCode") String languageCode){
return seedService.getCropVarietySelectionDTOBySearchString(searchString,languageCode);
}
Edit
Request on the frontend side is:
private basePath:string = this.apiUrl + "/seed";
getCropVarietySelectionDTOBySearchString(searchString: string):Observable<CropVarietyNameSelectionDTO[]>{
return (searchString && (searchString.trim().length > 0)) ? this.http.post<CropVarietyNameSelectionDTO[]>(this.basePath + "/get/varietynames/" + this.languageService.getCodeOfPreferredLanguage(), searchString) : Observable.of([]);
}
this.apiUrl = localhost:4200
That is not the correct way or option to use #PathVariable annotation which indicates that a method parameter should be bound to a URI template variable. You need to use #RequestParam annotation which indicates that a method parameter should be bound to a web request parameter. You can see this answer that is a #RequestParam vs #PathVariable
#GetMapping("/get/varietynames")
public List<CropXXXDTO> getXXXXXhString(#RequestParam #NotBlank
#Pattern(regexp = "^xx+$", message = "xxxxx")
#Size(min = 1, max = 40, message = "xxxxx") String searchString,
#RequestParam(required = false, defaultValue = "EN") String languageCode){
return seedService.getXXXXtring(searchString, languageCode);
}
Then you can check the URL by following way:
/get/varietynames?searchString=XXXXX&languageCode=EN

Bean validation - validate optional fields

Given a class that represents payload submitted from a form, I want to apply bean validation to a field that may or may not be present, for example:
class FormData {
#Pattern(...)
#Size(...)
#Whatever(...)
private String optionalField;
...
}
If optionalField is not sent in the payload, I don't want to apply any of the validators above, but if it is sent, I want to apply all of them. How can it be done?
Thanks.
So usually all of these constraints consider null value as valid. If your optional filed is null when it's not part of the payload all should work just fine as it is.
And for any mandatory fields you can put #NotNull on them.
EDIT
here's an example:
class FormData {
#Pattern(regexp = "\\d+")
#Size(min = 3, max = 3)
private final String optionalField;
#Pattern(regexp = "[a-z]+")
#Size(min = 3, max = 3)
#NotNull
private final String mandatoryField;
}
#Test
public void test() {
Validator validator = getValidator();
// optonal field is null so no violations will rise on it
FormData data = new FormData( null, "abc" );
Set<ConstraintViolation<FormData>> violations = validator.validate( data );
assertThat( violations ).isEmpty();
// optional field is present but it should fail the pattern validation:
data = new FormData( "aaa", "abc" );
violations = validator.validate( data );
assertThat( violations ).containsOnlyViolations(
violationOf( Pattern.class ).withProperty( "optionalField" )
);
}
You can see that in the first case you don't get any violations as the optional field is null. but in the second exmaple you receive a violation of pattern constraint as aaa is not a string of digits.

Strange TestNg DataProvider MethodMatcherException

The DataProvider returns exactly the number and type of arguments required by the method.
This error is strange because if I substitute change the parameter and argument to use List<Experiment> instead of an Experiment[] then no exception is raised.
The method is:
#Test(dataProvider = "provideExperimentId")
public void testDetermineExperimentId(Experiment[] experiments, int experimentId, int expected)
{
mockExperimentId(experiments, experimentId)
assertEquals(fileParser.determineExperimentId(), expected);
}
The mockExperimentId(Experiment[], int) method sets up some when-then clauses,
and uses the parameters to customise some return values.
The data provider is:
#DataProvider
private Object[][] provideExperimentId(){
MockitoAnnotations.initMocks(this);
return new Object[][]{
{new Experiment[]{mockedExperiment}, 1, 1}
};
}
For some reason, I get the following mismatch exception, even though the number of arguments and the types are in agreement.
org.testng.internal.reflect.MethodMatcherException:
Data provider mismatch
Method: testDetermineExperimentId([Parameter{index=0, type=[Lcom.redacted.Experiment;, declaredAnnotations=[]}, Parameter{index=1, type=int, declaredAnnotations=[]}, Parameter{index=2, type=int, declaredAnnotations=[]}])
Arguments: [([Lcom.redacted.Experiment;) [mockedExperiment],(java.lang.Integer) 1,(java.lang.Integer) 1]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Parameters.injectParameters(Parameters.java:796)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:973)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
at org.testng.SuiteRunner.run(SuiteRunner.java:364)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1187)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1116)
at org.testng.TestNG.runSuites(TestNG.java:1028)
at org.testng.TestNG.run(TestNG.java:996)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)
However, changing the method and DataProvider to use a list raises no exception
#Test(dataProvider = "provideExperimentId")
public void testDetermineExperimentId(List<Experiment> experiments, int experimentId, int expected)
{
mockExperimentId(experiments.toArray(new Experiment[0]), experimentId)
assertEquals(fileParser.determineExperimentId(), expected);
}
The mockExperimentId(Experiment[], int) method sets up some when-then clauses,
and uses the parameters to customise some return values
#DataProvider
private Object[][] provideExperimentId(){
MockitoAnnotations.initMocks(this);
return new Object[][]{
{Arrays.asList(mockedExperiment), 1, 1}
};
}

Grails command object validation gives exception instead of errors

I have command object like this:
#Validateable
class TaCustomerBoardActionCommand {
TaCustomerBoardAction action
static constraints = {
action casecade: true
}
}
and classes in command object below:
class TaCustomerBoardAction {
TaCustomerBoard taCustomerBoard
TaapAction taapAction
Date dateCreated // updated by grails
Date lastUpdated // updated by grails
User createdBy
OrgUnit orgUnit
Client client
static belongsTo = [Client]
static constraints = {
}
}
and
TaapAction {
int id
User createdUser
User responsibleUser
Brand brand
BusinessType businessType
Topic topic
Topic subTopic
String subject
String description
Date targetDate
int progress
String responsible
Client client
static belongsTo = [Client]
OrgUnit orgUnit
Date dateCreated // updated by grails
Date lastUpdated // updated by grails
TaapActionState taapActionState
static constraints = {
subject nullable: false, size: 1..64
description nullable: false, size: 1..4000
responsible nullable: false, size: 1..512
progress nullable: false
responsibleUser nullable:false
brand nullable:false
businessType nullable:false
topic nullable:false
subTopic nullable:false
targetDate nullable:false
}
TaCustomerBoard has similar constraints as above class.
but it gives exception instead of error codes.
Below is controller Post method:
def saveTaCustomerBoardAction(TaCustomerBoardActionCommand cmd){
if(cmd.validate()){
taActionPlanningService.saveAction(cmd.action.taapAction)
cmd.action.save(flush: true, failOnError: true)
}
[cmd:cmd]
}
Stack trace:
grails.validation.ValidationException: Validation Error(s) occurred
during save():
- Field error in object 'de.idare.move.taap.TaapAction' on field 'progress': rejected value [null]; codes
[de.idare.move.taap.TaapAction.progress.typeMismatch.error,de.idare.move.taap.TaapAction.progress.typeMismatch,taapAction.progress.typeMismatch.error,taapAction.progress.typeMismatch,typeMismatch.de.idare.move.taap.TaapAction.progress,typeMismatch.progress,typeMismatch.int,typeMismatch];
arguments [progress]; default message [Data Binding Failed]
- Field error in object 'de.idare.move.taap.TaapAction' on field 'description': rejected value [null]; codes
[de.idare.move.taap.TaapAction.description.nullable.error.de.idare.move.taap.TaapAction.description,de.idare.move.taap.TaapAction.description.nullable.error.description,de.idare.move.taap.TaapAction.description.nullable.error.java.lang.String,de.idare.move.taap.TaapAction.description.nullable.error,taapAction.description.nullable.error.de.idare.move.taap.TaapAction.description,taapAction.description.nullable.error.description,taapAction.description.nullable.error.java.lang.String,taapAction.description.nullable.error,de.idare.move.taap.TaapAction.description.nullable.de.idare.move.taap.TaapAction.description,de.idare.move.taap.TaapAction.description.nullable.description,de.idare.move.taap.TaapAction.description.nullable.java.lang.String,de.idare.move.taap.TaapAction.description.nullable,taapAction.description.nullable.de.idare.move.taap.TaapAction.description,taapAction.description.nullable.description,taapAction.description.nullable.java.lang.String,taapAction.description.nullable,nullable.de.idare.move.taap.TaapAction.description,nullable.description,nullable.java.lang.String,nullable];
arguments [description,class de.idare.move.taap.TaapAction]; default
message [Property [{0}] of class [{1}] can not be null]
Kindly help me I am stuck with this problem.
Your problem is rather straight forward. Well it would seem, you have provided how things work but not actually provided what is sent. My suggestion is to do a println params in the controller action using validation method to see what it is sent / and validated.
You have declared progress as int and not Integer. This means it can't be nullable. Always use Boolean Integer or whatever the case maybe if something is meant to be nullable. Secondly you have also declared description and progress as nullable false meaning they have to be provided. The error message suggests command sent does not have a progress or description sent to it as part of the validation. This is something you need to investigate further by simple debugging such as println at your end to figure out why that is the case.
int progress
...
static constraints = {
progress nullable: false
description nullable: false, size: 1..4000
}
Just remove the failOnError: true. You'll be able to process error objects instead of catching exception.
Documentation

Resources