Add AssertJ support for the Model

This commit adds AssertJ compatible assertions for the Model that is
generated from an HTTP request.

See gh-21178
This commit is contained in:
Stéphane Nicoll
2024-03-15 13:26:53 +01:00
parent b46e528922
commit 1cdbcc58f3
5 changed files with 607 additions and 0 deletions

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}