Browse Source
This commit adds AssertJ compatible assertions for the Model that is generated from an HTTP request. See gh-21178pull/32467/head
5 changed files with 607 additions and 0 deletions
@ -0,0 +1,123 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2024 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.test.validation; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.assertj.core.api.AbstractAssert; |
||||||
|
import org.assertj.core.api.AssertProvider; |
||||||
|
import org.assertj.core.api.Assertions; |
||||||
|
import org.assertj.core.api.ListAssert; |
||||||
|
import org.assertj.core.error.BasicErrorMessageFactory; |
||||||
|
import org.assertj.core.internal.Failures; |
||||||
|
|
||||||
|
import org.springframework.validation.BindingResult; |
||||||
|
import org.springframework.validation.FieldError; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||||
|
* {@link BindingResult}. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
* @since 6.2 |
||||||
|
* @param <SELF> the type of assertions |
||||||
|
*/ |
||||||
|
public abstract class AbstractBindingResultAssert<SELF extends AbstractBindingResultAssert<SELF>> extends AbstractAssert<SELF, BindingResult> { |
||||||
|
|
||||||
|
private final Failures failures = Failures.instance(); |
||||||
|
|
||||||
|
private final String name; |
||||||
|
|
||||||
|
protected AbstractBindingResultAssert(String name, BindingResult bindingResult, Class<?> selfType) { |
||||||
|
super(bindingResult, selfType); |
||||||
|
this.name = name; |
||||||
|
as("Binding result for attribute '%s", this.name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the total number of errors is equal to the given one. |
||||||
|
* @param expected the expected number of errors |
||||||
|
*/ |
||||||
|
public SELF hasErrorsCount(int expected) { |
||||||
|
assertThat(this.actual.getErrorCount()) |
||||||
|
.as("check errors for attribute '%s'", this.name).isEqualTo(expected); |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the actual binding result contains fields in error with the |
||||||
|
* given {@code fieldNames}. |
||||||
|
* @param fieldNames the names of fields that should be in error |
||||||
|
*/ |
||||||
|
public SELF hasFieldErrors(String... fieldNames) { |
||||||
|
assertThat(fieldErrorNames()).contains(fieldNames); |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the actual binding result contains <em>only</em> fields in |
||||||
|
* error with the given {@code fieldNames}, and nothing else. |
||||||
|
* @param fieldNames the exhaustive list of field name that should be in error |
||||||
|
*/ |
||||||
|
public SELF hasOnlyFieldErrors(String... fieldNames) { |
||||||
|
assertThat(fieldErrorNames()).containsOnly(fieldNames); |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the field with the given {@code fieldName} has an error |
||||||
|
* matching the given {@code errorCode}. |
||||||
|
* @param fieldName the name of a field in error |
||||||
|
* @param errorCode the error code for that field |
||||||
|
*/ |
||||||
|
public SELF hasFieldErrorCode(String fieldName, String errorCode) { |
||||||
|
Assertions.assertThat(getFieldError(fieldName).getCode()) |
||||||
|
.as("check error code for field '%s'", fieldName).isEqualTo(errorCode); |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
protected AssertionError unexpectedBindingResult(String reason, Object... arguments) { |
||||||
|
return this.failures.failure(this.info, new UnexpectedBindingResult(reason, arguments)); |
||||||
|
} |
||||||
|
|
||||||
|
private AssertProvider<ListAssert<String>> fieldErrorNames() { |
||||||
|
return () -> { |
||||||
|
List<String> actual = this.actual.getFieldErrors().stream().map(FieldError::getField).toList(); |
||||||
|
return new ListAssert<>(actual).as("check field errors"); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private FieldError getFieldError(String fieldName) { |
||||||
|
FieldError fieldError = this.actual.getFieldError(fieldName); |
||||||
|
if (fieldError == null) { |
||||||
|
throw unexpectedBindingResult("to have at least an error for field '%s'", fieldName); |
||||||
|
} |
||||||
|
return fieldError; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private final class UnexpectedBindingResult extends BasicErrorMessageFactory { |
||||||
|
|
||||||
|
private UnexpectedBindingResult(String reason, Object... arguments) { |
||||||
|
super("%nExpecting binding result:%n %s%n%s", AbstractBindingResultAssert.this.actual, |
||||||
|
reason.formatted(arguments)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
/** |
||||||
|
* Testing support for validation. |
||||||
|
*/ |
||||||
|
@NonNullApi |
||||||
|
@NonNullFields |
||||||
|
package org.springframework.test.validation; |
||||||
|
|
||||||
|
import org.springframework.lang.NonNullApi; |
||||||
|
import org.springframework.lang.NonNullFields; |
||||||
@ -0,0 +1,163 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2024 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.test.web.servlet.assertj; |
||||||
|
|
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.function.Predicate; |
||||||
|
|
||||||
|
import org.assertj.core.api.AbstractMapAssert; |
||||||
|
import org.assertj.core.error.BasicErrorMessageFactory; |
||||||
|
import org.assertj.core.internal.Failures; |
||||||
|
|
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.test.validation.AbstractBindingResultAssert; |
||||||
|
import org.springframework.validation.BindingResult; |
||||||
|
import org.springframework.validation.BindingResultUtils; |
||||||
|
import org.springframework.validation.Errors; |
||||||
|
import org.springframework.web.servlet.ModelAndView; |
||||||
|
|
||||||
|
/** |
||||||
|
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||||
|
* a {@linkplain ModelAndView#getModel() model}. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
* @since 6.2 |
||||||
|
*/ |
||||||
|
public class ModelAssert extends AbstractMapAssert<ModelAssert, Map<String, Object>, String, Object> { |
||||||
|
|
||||||
|
private final Failures failures = Failures.instance(); |
||||||
|
|
||||||
|
public ModelAssert(Map<String, Object> map) { |
||||||
|
super(map, ModelAssert.class); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a new {@linkplain AbstractBindingResultAssert assertion} object |
||||||
|
* that uses the {@link BindingResult} with the given {@code name} as the |
||||||
|
* object to test. |
||||||
|
* Examples: <pre><code class='java'> |
||||||
|
* // Check that the "person" attribute in the model has 2 errors:
|
||||||
|
* assertThat(...).model().extractingBindingResult("person").hasErrorsCount(2); |
||||||
|
* </code></pre> |
||||||
|
*/ |
||||||
|
public AbstractBindingResultAssert<?> extractingBindingResult(String name) { |
||||||
|
BindingResult result = BindingResultUtils.getBindingResult(this.actual, name); |
||||||
|
if (result == null) { |
||||||
|
throw unexpectedModel("to have a binding result for attribute '%s'", name); |
||||||
|
} |
||||||
|
return new BindingResultAssert(name, result); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the actual model has at least one error. |
||||||
|
*/ |
||||||
|
public ModelAssert hasErrors() { |
||||||
|
if (getAllErrors() == 0) { |
||||||
|
throw unexpectedModel("to have at least one error"); |
||||||
|
} |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the actual model does not have any errors. |
||||||
|
*/ |
||||||
|
public ModelAssert doesNotHaveErrors() { |
||||||
|
int count = getAllErrors(); if (count > 0) { |
||||||
|
throw unexpectedModel("to not have an error, but got %s", count); |
||||||
|
} |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the actual model contain the attributes with the given |
||||||
|
* {@code names}, and that these attributes have each at least one error. |
||||||
|
* @param names the expected names of attributes with errors |
||||||
|
*/ |
||||||
|
public ModelAssert hasAttributeErrors(String... names) { |
||||||
|
return assertAttributes(names, BindingResult::hasErrors, |
||||||
|
"to have attribute errors for", "these attributes do not have any error"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that the actual model contain the attributes with the given |
||||||
|
* {@code names}, and that these attributes do not have any error. |
||||||
|
* @param names the expected names of attributes without errors |
||||||
|
*/ |
||||||
|
public ModelAssert doesNotHaveAttributeErrors(String... names) { |
||||||
|
return assertAttributes(names, Predicate.not(BindingResult::hasErrors), |
||||||
|
"to have attribute without errors for", "these attributes have at least an error"); |
||||||
|
} |
||||||
|
|
||||||
|
private ModelAssert assertAttributes(String[] names, Predicate<BindingResult> condition, |
||||||
|
String assertionMessage, String failAssertionMessage) { |
||||||
|
|
||||||
|
Set<String> missing = new LinkedHashSet<>(); |
||||||
|
Set<String> failCondition = new LinkedHashSet<>(); |
||||||
|
for (String name : names) { |
||||||
|
BindingResult bindingResult = getBindingResult(name); |
||||||
|
if (bindingResult == null) { |
||||||
|
missing.add(name); |
||||||
|
} |
||||||
|
else if (!condition.test(bindingResult)) { |
||||||
|
failCondition.add(name); |
||||||
|
} |
||||||
|
} |
||||||
|
if (!missing.isEmpty() || !failCondition.isEmpty()) { |
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
sb.append("%n%s:%n %s%n".formatted(assertionMessage, String.join(", ", names))); |
||||||
|
if (!missing.isEmpty()) { |
||||||
|
sb.append("%nbut could not find these attributes:%n %s%n".formatted(String.join(", ", missing))); |
||||||
|
} |
||||||
|
if (!failCondition.isEmpty()) { |
||||||
|
String prefix = missing.isEmpty() ? "but" : "and"; |
||||||
|
sb.append("%n%s %s:%n %s%n".formatted(prefix, failAssertionMessage, String.join(", ", failCondition))); |
||||||
|
} |
||||||
|
throw unexpectedModel(sb.toString()); |
||||||
|
} |
||||||
|
return this.myself; |
||||||
|
} |
||||||
|
|
||||||
|
private AssertionError unexpectedModel(String reason, Object... arguments) { |
||||||
|
return this.failures.failure(this.info, new UnexpectedModel(reason, arguments)); |
||||||
|
} |
||||||
|
|
||||||
|
private int getAllErrors() { |
||||||
|
return this.actual.values().stream().filter(Errors.class::isInstance).map(Errors.class::cast) |
||||||
|
.map(Errors::getErrorCount).reduce(0, Integer::sum); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private BindingResult getBindingResult(String name) { |
||||||
|
return BindingResultUtils.getBindingResult(this.actual, name); |
||||||
|
} |
||||||
|
|
||||||
|
private final class UnexpectedModel extends BasicErrorMessageFactory { |
||||||
|
|
||||||
|
private UnexpectedModel(String reason, Object... arguments) { |
||||||
|
super("%nExpecting model:%n %s%n%s", ModelAssert.this.actual, reason.formatted(arguments)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static final class BindingResultAssert extends AbstractBindingResultAssert<BindingResultAssert> { |
||||||
|
public BindingResultAssert(String name, BindingResult bindingResult) { |
||||||
|
super(name, bindingResult, BindingResultAssert.class); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,136 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2024 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.test.validation; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.assertj.core.api.AssertProvider; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.beans.MutablePropertyValues; |
||||||
|
import org.springframework.beans.testfixture.beans.TestBean; |
||||||
|
import org.springframework.validation.BindException; |
||||||
|
import org.springframework.validation.BindingResult; |
||||||
|
import org.springframework.validation.DataBinder; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link AbstractBindingResultAssert}. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
*/ |
||||||
|
class AbstractBindingResultAssertTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorsCountWithNoError() { |
||||||
|
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "42"))).hasErrorsCount(0); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorsCountWithInvalidCount() { |
||||||
|
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), |
||||||
|
Map.of("name", "John", "age", "4x", "touchy", "invalid.value")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasErrorsCount(1)) |
||||||
|
.withMessageContainingAll("check errors for attribute 'test'", "1", "2"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasFieldErrorsWithMatchingSubset() { |
||||||
|
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||||
|
.hasFieldErrors("touchy"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasFieldErrorsWithAllMatching() { |
||||||
|
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||||
|
.hasFieldErrors("touchy", "age"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasFieldErrorsWithNotAllMatching() { |
||||||
|
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasFieldErrors("age", "name")) |
||||||
|
.withMessageContainingAll("check field errors", "age", "touchy", "name"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasOnlyFieldErrorsWithAllMatching() { |
||||||
|
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||||
|
.hasOnlyFieldErrors("touchy", "age"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasOnlyFieldErrorsWithMatchingSubset() { |
||||||
|
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasOnlyFieldErrors("age")) |
||||||
|
.withMessageContainingAll("check field errors", "age", "touchy"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasFieldErrorCodeWithMatchingCode() { |
||||||
|
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||||
|
.hasFieldErrorCode("age", "typeMismatch"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasFieldErrorCodeWitNonMatchingCode() { |
||||||
|
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasFieldErrorCode("age", "castFailure")) |
||||||
|
.withMessageContainingAll("check error code for field 'age'", "castFailure", "typeMismatch"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasFieldErrorCodeWitNonMatchingField() { |
||||||
|
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasFieldErrorCode("unknown", "whatever")) |
||||||
|
.withMessageContainingAll("Expecting binding result", "touchy", "age", |
||||||
|
"to have at least an error for field 'unknown'"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private AssertProvider<BindingResultAssert> bindingResult(Object instance, Map<String, Object> propertyValues) { |
||||||
|
return () -> new BindingResultAssert("test", createBindingResult(instance, propertyValues)); |
||||||
|
} |
||||||
|
|
||||||
|
private static BindingResult createBindingResult(Object instance, Map<String, Object> propertyValues) { |
||||||
|
DataBinder binder = new DataBinder(instance, "test"); |
||||||
|
MutablePropertyValues pvs = new MutablePropertyValues(propertyValues); |
||||||
|
binder.bind(pvs); |
||||||
|
try { |
||||||
|
binder.close(); |
||||||
|
return binder.getBindingResult(); |
||||||
|
} |
||||||
|
catch (BindException ex) { |
||||||
|
return ex.getBindingResult(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static final class BindingResultAssert extends AbstractBindingResultAssert<BindingResultAssert> { |
||||||
|
public BindingResultAssert(String name, BindingResult bindingResult) { |
||||||
|
super(name, bindingResult, BindingResultAssert.class); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,176 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2024 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.test.web.servlet.assertj; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.assertj.core.api.AssertProvider; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.beans.MutablePropertyValues; |
||||||
|
import org.springframework.beans.testfixture.beans.TestBean; |
||||||
|
import org.springframework.validation.BindException; |
||||||
|
import org.springframework.validation.DataBinder; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link ModelAssert}. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
*/ |
||||||
|
class ModelAssertTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrors() { |
||||||
|
assertThat(forModel(new TestBean(), Map.of("name", "John", "age", "4x"))).hasErrors(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorsWithNoError() { |
||||||
|
AssertProvider<ModelAssert> actual = forModel(new TestBean(), Map.of("name", "John", "age", "42")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(actual).hasErrors()) |
||||||
|
.withMessageContainingAll("John", "to have at least one error"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void doesNotHaveErrors() { |
||||||
|
assertThat(forModel(new TestBean(), Map.of("name", "John", "age", "42"))).doesNotHaveErrors(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void doesNotHaveErrorsWithError() { |
||||||
|
AssertProvider<ModelAssert> actual = forModel(new TestBean(), Map.of("name", "John", "age", "4x")); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(actual).doesNotHaveErrors()) |
||||||
|
.withMessageContainingAll("John", "to not have an error, but got 1"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void extractBindingResultForAttributeInError() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "person", new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "invalid.value")); |
||||||
|
assertThat(forModel(model)).extractingBindingResult("person").hasErrorsCount(2); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorCountForUnknownAttribute() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "person", new TestBean(), Map.of("name", "John", "age", "42")); |
||||||
|
AssertProvider<ModelAssert> actual = forModel(model); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).extractingBindingResult("user")) |
||||||
|
.withMessageContainingAll("to have a binding result for attribute 'user'"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorsWithMatchingAttributes() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "wrong1", new TestBean(), Map.of("name", "first", "age", "4x")); |
||||||
|
augmentModel(model, "valid", new TestBean(), Map.of("name", "second")); |
||||||
|
augmentModel(model, "wrong2", new TestBean(), Map.of("name", "third", "touchy", "invalid.name")); |
||||||
|
assertThat(forModel(model)).hasAttributeErrors("wrong1", "wrong2"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorsWithOneNonMatchingAttribute() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "wrong1", new TestBean(), Map.of("name", "first", "age", "4x")); |
||||||
|
augmentModel(model, "valid", new TestBean(), Map.of("name", "second")); |
||||||
|
augmentModel(model, "wrong2", new TestBean(), Map.of("name", "third", "touchy", "invalid.name")); |
||||||
|
AssertProvider<ModelAssert> actual = forModel(model); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasAttributeErrors("wrong1", "valid")) |
||||||
|
.withMessageContainingAll("to have attribute errors for:", "wrong1, valid", |
||||||
|
"but these attributes do not have any error:", "valid"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void hasErrorsWithOneNonMatchingAttributeAndOneUnknownAttribute() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "wrong1", new TestBean(), Map.of("name", "first", "age", "4x")); |
||||||
|
augmentModel(model, "valid", new TestBean(), Map.of("name", "second")); |
||||||
|
augmentModel(model, "wrong2", new TestBean(), Map.of("name", "third", "touchy", "invalid.name")); |
||||||
|
AssertProvider<ModelAssert> actual = forModel(model); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).hasAttributeErrors("wrong1", "unknown", "valid")) |
||||||
|
.withMessageContainingAll("to have attribute errors for:", "wrong1, unknown, valid", |
||||||
|
"but could not find these attributes:", "unknown", |
||||||
|
"and these attributes do not have any error:", "valid"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void doesNotHaveErrorsWithMatchingAttributes() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "valid1", new TestBean(), Map.of("name", "first")); |
||||||
|
augmentModel(model, "wrong", new TestBean(), Map.of("name", "second", "age", "4x")); |
||||||
|
augmentModel(model, "valid2", new TestBean(), Map.of("name", "third")); |
||||||
|
assertThat(forModel(model)).doesNotHaveAttributeErrors("valid1", "valid2"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void doesNotHaveErrorsWithOneNonMatchingAttribute() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "valid1", new TestBean(), Map.of("name", "first")); |
||||||
|
augmentModel(model, "wrong", new TestBean(), Map.of("name", "second", "age", "4x")); |
||||||
|
augmentModel(model, "valid2", new TestBean(), Map.of("name", "third")); |
||||||
|
AssertProvider<ModelAssert> actual = forModel(model); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).doesNotHaveAttributeErrors("valid1", "wrong")) |
||||||
|
.withMessageContainingAll("to have attribute without errors for:", "valid1, wrong", |
||||||
|
"but these attributes have at least an error:", "wrong"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void doesNotHaveErrorsWithOneNonMatchingAttributeAndOneUnknownAttribute() { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "valid1", new TestBean(), Map.of("name", "first")); |
||||||
|
augmentModel(model, "wrong", new TestBean(), Map.of("name", "second", "age", "4x")); |
||||||
|
augmentModel(model, "valid2", new TestBean(), Map.of("name", "third")); |
||||||
|
AssertProvider<ModelAssert> actual = forModel(model); |
||||||
|
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||||
|
() -> assertThat(actual).doesNotHaveAttributeErrors("valid1", "unknown", "wrong")) |
||||||
|
.withMessageContainingAll("to have attribute without errors for:", "valid1, unknown, wrong", |
||||||
|
"but could not find these attributes:", "unknown", |
||||||
|
"and these attributes have at least an error:", "wrong"); |
||||||
|
} |
||||||
|
|
||||||
|
private AssertProvider<ModelAssert> forModel(Map<String, Object> model) { |
||||||
|
return () -> new ModelAssert(model); |
||||||
|
} |
||||||
|
|
||||||
|
private AssertProvider<ModelAssert> forModel(Object instance, Map<String, Object> propertyValues) { |
||||||
|
Map<String, Object> model = new HashMap<>(); |
||||||
|
augmentModel(model, "test", instance, propertyValues); |
||||||
|
return forModel(model); |
||||||
|
} |
||||||
|
|
||||||
|
private static void augmentModel(Map<String, Object> model, String attribute, Object instance, Map<String, Object> propertyValues) { |
||||||
|
DataBinder binder = new DataBinder(instance, attribute); |
||||||
|
MutablePropertyValues pvs = new MutablePropertyValues(propertyValues); |
||||||
|
binder.bind(pvs); |
||||||
|
try { |
||||||
|
binder.close(); |
||||||
|
model.putAll(binder.getBindingResult().getModel()); |
||||||
|
} |
||||||
|
catch (BindException ex) { |
||||||
|
model.putAll(ex.getBindingResult().getModel()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue