001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.web.servlet.result;
018
019import org.hamcrest.Matcher;
020
021import org.springframework.test.web.servlet.MvcResult;
022import org.springframework.test.web.servlet.ResultMatcher;
023import org.springframework.ui.ModelMap;
024import org.springframework.validation.BindingResult;
025import org.springframework.validation.Errors;
026import org.springframework.validation.FieldError;
027import org.springframework.web.servlet.ModelAndView;
028
029import static org.hamcrest.MatcherAssert.assertThat;
030import static org.springframework.test.util.AssertionErrors.assertEquals;
031import static org.springframework.test.util.AssertionErrors.assertFalse;
032import static org.springframework.test.util.AssertionErrors.assertNotNull;
033import static org.springframework.test.util.AssertionErrors.assertNull;
034import static org.springframework.test.util.AssertionErrors.assertTrue;
035
036/**
037 * Factory for assertions on the model.
038 *
039 * <p>An instance of this class is typically accessed via
040 * {@link MockMvcResultMatchers#model}.
041 *
042 * @author Rossen Stoyanchev
043 * @author Sam Brannen
044 * @since 3.2
045 */
046public class ModelResultMatchers {
047
048        /**
049         * Protected constructor.
050         * Use {@link MockMvcResultMatchers#model()}.
051         */
052        protected ModelResultMatchers() {
053        }
054
055
056        /**
057         * Assert a model attribute value with the given Hamcrest {@link Matcher}.
058         */
059        @SuppressWarnings("unchecked")
060        public <T> ResultMatcher attribute(String name, Matcher<T> matcher) {
061                return result -> {
062                        ModelAndView mav = getModelAndView(result);
063                        assertThat("Model attribute '" + name + "'", (T) mav.getModel().get(name), matcher);
064                };
065        }
066
067        /**
068         * Assert a model attribute value.
069         */
070        public ResultMatcher attribute(String name, Object value) {
071                return result -> {
072                        ModelAndView mav = getModelAndView(result);
073                        assertEquals("Model attribute '" + name + "'", value, mav.getModel().get(name));
074                };
075        }
076
077        /**
078         * Assert the given model attributes exist.
079         */
080        public ResultMatcher attributeExists(String... names) {
081                return result -> {
082                        ModelAndView mav = getModelAndView(result);
083                        for (String name : names) {
084                                assertNotNull("Model attribute '" + name + "' does not exist", mav.getModel().get(name));
085                        }
086                };
087        }
088
089        /**
090         * Assert the given model attributes do not exist.
091         */
092        public ResultMatcher attributeDoesNotExist(String... names) {
093                return result -> {
094                        ModelAndView mav = getModelAndView(result);
095                        for (String name : names) {
096                                assertNull("Model attribute '" + name + "' exists", mav.getModel().get(name));
097                        }
098                };
099        }
100
101        /**
102         * Assert the given model attribute(s) have errors.
103         */
104        public ResultMatcher attributeErrorCount(String name, int expectedCount) {
105                return result -> {
106                        ModelAndView mav = getModelAndView(result);
107                        Errors errors = getBindingResult(mav, name);
108                        assertEquals("Binding/validation error count for attribute '" + name + "',",
109                                        expectedCount, errors.getErrorCount());
110                };
111        }
112
113        /**
114         * Assert the given model attribute(s) have errors.
115         */
116        public ResultMatcher attributeHasErrors(String... names) {
117                return mvcResult -> {
118                        ModelAndView mav = getModelAndView(mvcResult);
119                        for (String name : names) {
120                                BindingResult result = getBindingResult(mav, name);
121                                assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
122                        }
123                };
124        }
125
126        /**
127         * Assert the given model attribute(s) do not have errors.
128         */
129        public ResultMatcher attributeHasNoErrors(String... names) {
130                return mvcResult -> {
131                        ModelAndView mav = getModelAndView(mvcResult);
132                        for (String name : names) {
133                                BindingResult result = getBindingResult(mav, name);
134                                assertFalse("Unexpected errors for attribute '" + name + "': " + result.getAllErrors(),
135                                                result.hasErrors());
136                        }
137                };
138        }
139
140        /**
141         * Assert the given model attribute field(s) have errors.
142         */
143        public ResultMatcher attributeHasFieldErrors(String name, String... fieldNames) {
144                return mvcResult -> {
145                        ModelAndView mav = getModelAndView(mvcResult);
146                        BindingResult result = getBindingResult(mav, name);
147                        assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
148                        for (String fieldName : fieldNames) {
149                                boolean hasFieldErrors = result.hasFieldErrors(fieldName);
150                                assertTrue("No errors for field '" + fieldName + "' of attribute '" + name + "'", hasFieldErrors);
151                        }
152                };
153        }
154
155        /**
156         * Assert a field error code for a model attribute using exact String match.
157         * @since 4.1
158         */
159        public ResultMatcher attributeHasFieldErrorCode(String name, String fieldName, String error) {
160                return mvcResult -> {
161                        ModelAndView mav = getModelAndView(mvcResult);
162                        BindingResult result = getBindingResult(mav, name);
163                        assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
164                        FieldError fieldError = result.getFieldError(fieldName);
165                        assertNotNull("No errors for field '" + fieldName + "' of attribute '" + name + "'", fieldError);
166                        String code = fieldError.getCode();
167                        assertEquals("Field error code", error, code);
168                };
169        }
170
171        /**
172         * Assert a field error code for a model attribute using a {@link org.hamcrest.Matcher}.
173         * @since 4.1
174         */
175        public ResultMatcher attributeHasFieldErrorCode(String name, String fieldName,
176                        Matcher<? super String> matcher) {
177
178                return mvcResult -> {
179                        ModelAndView mav = getModelAndView(mvcResult);
180                        BindingResult result = getBindingResult(mav, name);
181                        assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
182                        FieldError fieldError = result.getFieldError(fieldName);
183                        assertNotNull("No errors for field '" + fieldName + "' of attribute '" + name + "'", fieldError);
184                        String code = fieldError.getCode();
185                        assertThat("Field name '" + fieldName + "' of attribute '" + name + "'", code, matcher);
186                };
187        }
188
189        /**
190         * Assert the total number of errors in the model.
191         */
192        public ResultMatcher errorCount(int expectedCount) {
193                return result -> {
194                        int actualCount = getErrorCount(getModelAndView(result).getModelMap());
195                        assertEquals("Binding/validation error count", expectedCount, actualCount);
196                };
197        }
198
199        /**
200         * Assert the model has errors.
201         */
202        public ResultMatcher hasErrors() {
203                return result -> {
204                        int count = getErrorCount(getModelAndView(result).getModelMap());
205                        assertTrue("Expected binding/validation errors", count != 0);
206                };
207        }
208
209        /**
210         * Assert the model has no errors.
211         */
212        public ResultMatcher hasNoErrors() {
213                return result -> {
214                        ModelAndView mav = getModelAndView(result);
215                        for (Object value : mav.getModel().values()) {
216                                if (value instanceof Errors) {
217                                        assertFalse("Unexpected binding/validation errors: " + value, ((Errors) value).hasErrors());
218                                }
219                        }
220                };
221        }
222
223        /**
224         * Assert the number of model attributes.
225         */
226        public ResultMatcher size(int size) {
227                return result -> {
228                        ModelAndView mav = getModelAndView(result);
229                        int actual = 0;
230                        for (String key : mav.getModel().keySet()) {
231                                if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
232                                        actual++;
233                                }
234                        }
235                        assertEquals("Model size", size, actual);
236                };
237        }
238
239        private ModelAndView getModelAndView(MvcResult mvcResult) {
240                ModelAndView mav = mvcResult.getModelAndView();
241                assertNotNull("No ModelAndView found", mav);
242                return mav;
243        }
244
245        private BindingResult getBindingResult(ModelAndView mav, String name) {
246                BindingResult result = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name);
247                assertNotNull("No BindingResult for attribute: " + name, result);
248                return result;
249        }
250
251        private int getErrorCount(ModelMap model) {
252                int count = 0;
253                for (Object value : model.values()) {
254                        if (value instanceof Errors) {
255                                count += ((Errors) value).getErrorCount();
256                        }
257                }
258                return count;
259        }
260
261}