001/*
002 * Copyright 2002-2017 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.web.servlet.ModelAndView;
027
028import static org.hamcrest.MatcherAssert.*;
029import static org.springframework.test.util.AssertionErrors.*;
030
031/**
032 * Factory for assertions on the model.
033 *
034 * <p>An instance of this class is typically accessed via
035 * {@link MockMvcResultMatchers#model}.
036 *
037 * @author Rossen Stoyanchev
038 * @since 3.2
039 */
040public class ModelResultMatchers {
041
042        /**
043         * Protected constructor.
044         * Use {@link MockMvcResultMatchers#model()}.
045         */
046        protected ModelResultMatchers() {
047        }
048
049
050        /**
051         * Assert a model attribute value with the given Hamcrest {@link Matcher}.
052         */
053        public <T> ResultMatcher attribute(final String name, final Matcher<T> matcher) {
054                return new ResultMatcher() {
055                        @Override
056                        @SuppressWarnings("unchecked")
057                        public void match(MvcResult result) throws Exception {
058                                ModelAndView mav = getModelAndView(result);
059                                assertThat("Model attribute '" + name + "'", (T) mav.getModel().get(name), matcher);
060                        }
061                };
062        }
063
064        /**
065         * Assert a model attribute value.
066         */
067        public ResultMatcher attribute(final String name, final Object value) {
068                return new ResultMatcher() {
069                        @Override
070                        public void match(MvcResult result) throws Exception {
071                                ModelAndView mav = getModelAndView(result);
072                                assertEquals("Model attribute '" + name + "'", value, mav.getModel().get(name));
073                        }
074                };
075        }
076
077        /**
078         * Assert the given model attributes exist.
079         */
080        public ResultMatcher attributeExists(final String... names) {
081                return new ResultMatcher() {
082                        @Override
083                        public void match(MvcResult result) throws Exception {
084                                ModelAndView mav = getModelAndView(result);
085                                for (String name : names) {
086                                        assertTrue("Model attribute '" + name + "' does not exist", mav.getModel().get(name) != null);
087                                }
088                        }
089                };
090        }
091
092    /**
093     * Assert the given model attributes do not exist
094     */
095    public ResultMatcher attributeDoesNotExist(final String... names) {
096        return new ResultMatcher() {
097            @Override
098            public void match(MvcResult result) throws Exception {
099                ModelAndView mav = getModelAndView(result);
100                for (String name : names) {
101                    assertTrue("Model attribute '" + name + "' exists", mav.getModel().get(name) == null);
102                }
103            }
104        };
105    }
106
107        /**
108         * Assert the given model attribute(s) have errors.
109         */
110        public ResultMatcher attributeErrorCount(final String name, final int expectedCount) {
111                return new ResultMatcher() {
112                        public void match(MvcResult result) throws Exception {
113                                ModelAndView mav = getModelAndView(result);
114                                Errors errors = getBindingResult(mav, name);
115                                assertEquals("Binding/validation error count for attribute '" + name + "', ",
116                                                expectedCount, errors.getErrorCount());
117                        }
118                };
119        }
120
121        /**
122         * Assert the given model attribute(s) have errors.
123         */
124        public ResultMatcher attributeHasErrors(final String... names) {
125                return new ResultMatcher() {
126                        public void match(MvcResult mvcResult) throws Exception {
127                                ModelAndView mav = getModelAndView(mvcResult);
128                                for (String name : names) {
129                                        BindingResult result = getBindingResult(mav, name);
130                                        assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
131                                }
132                        }
133                };
134        }
135
136        /**
137         * Assert the given model attribute(s) do not have errors.
138         */
139        public ResultMatcher attributeHasNoErrors(final String... names) {
140                return new ResultMatcher() {
141                        public void match(MvcResult mvcResult) throws Exception {
142                                ModelAndView mav = getModelAndView(mvcResult);
143                                for (String name : names) {
144                                        BindingResult result = getBindingResult(mav, name);
145                                        assertTrue("Unexpected errors for attribute '" + name + "': " + result.getAllErrors(),
146                                                        !result.hasErrors());
147                                }
148                        }
149                };
150        }
151
152        /**
153         * Assert the given model attribute field(s) have errors.
154         */
155        public ResultMatcher attributeHasFieldErrors(final String name, final String... fieldNames) {
156                return new ResultMatcher() {
157                        public void match(MvcResult mvcResult) throws Exception {
158                                ModelAndView mav = getModelAndView(mvcResult);
159                                BindingResult result = getBindingResult(mav, name);
160                                assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
161                                for (final String fieldName : fieldNames) {
162                                        boolean hasFieldErrors = result.hasFieldErrors(fieldName);
163                                        assertTrue("No errors for field '" + fieldName + "' of attribute '" + name + "'", hasFieldErrors);
164                                }
165                        }
166                };
167        }
168
169        /**
170         * Assert a field error code for a model attribute using exact String match.
171         * @since 4.1
172         */
173        public ResultMatcher attributeHasFieldErrorCode(final String name, final String fieldName, final String error) {
174                return new ResultMatcher() {
175                        public void match(MvcResult mvcResult) throws Exception {
176                                ModelAndView mav = getModelAndView(mvcResult);
177                                BindingResult result = getBindingResult(mav, name);
178                                assertTrue("No errors for attribute '" + name + "'", result.hasErrors());
179                                boolean hasFieldErrors = result.hasFieldErrors(fieldName);
180                                assertTrue("No errors for field '" + fieldName + "' of attribute '" + name + "'", hasFieldErrors);
181                                String code = result.getFieldError(fieldName).getCode();
182                                assertTrue("Expected error code '" + error + "' but got '" + code + "'", code.equals(error));
183                        }
184                };
185        }
186
187        /**
188         * Assert a field error code for a model attribute using a {@link org.hamcrest.Matcher}.
189         * @since 4.1
190         */
191        public <T> ResultMatcher attributeHasFieldErrorCode(final String name, final String fieldName,
192                        final Matcher<? super String> matcher) {
193
194                return new ResultMatcher() {
195                        @Override
196                        public void match(MvcResult mvcResult) throws Exception {
197                                ModelAndView mav = getModelAndView(mvcResult);
198                                BindingResult result = getBindingResult(mav, name);
199                                assertTrue("No errors for attribute: [" + name + "]", result.hasErrors());
200                                boolean hasFieldErrors = result.hasFieldErrors(fieldName);
201                                assertTrue("No errors for field '" + fieldName + "' of attribute '" + name + "'", hasFieldErrors);
202                                String code = result.getFieldError(fieldName).getCode();
203                                assertThat("Field name '" + fieldName + "' of attribute '" + name + "'", code, matcher);
204                        }
205                };
206        }
207
208        /**
209         * Assert the total number of errors in the model.
210         */
211        public <T> ResultMatcher errorCount(final int expectedCount) {
212                return new ResultMatcher() {
213                        @Override
214                        public void match(MvcResult result) throws Exception {
215                                int actualCount = getErrorCount(getModelAndView(result).getModelMap());
216                                assertEquals("Binding/validation error count", expectedCount, actualCount);
217                        }
218                };
219        }
220
221        /**
222         * Assert the model has errors.
223         */
224        public <T> ResultMatcher hasErrors() {
225                return new ResultMatcher() {
226                        @Override
227                        public void match(MvcResult result) throws Exception {
228                                int count = getErrorCount(getModelAndView(result).getModelMap());
229                                assertTrue("Expected binding/validation errors", count != 0);
230                        }
231                };
232        }
233
234        /**
235         * Assert the model has no errors.
236         */
237        public <T> ResultMatcher hasNoErrors() {
238                return new ResultMatcher() {
239                        @Override
240                        public void match(MvcResult result) throws Exception {
241                                ModelAndView mav = getModelAndView(result);
242                                for (Object value : mav.getModel().values()) {
243                                        if (value instanceof Errors) {
244                                                assertTrue("Unexpected binding/validation errors: " + value, !((Errors) value).hasErrors());
245                                        }
246                                }
247                        }
248                };
249        }
250
251        /**
252         * Assert the number of model attributes.
253         */
254        public <T> ResultMatcher size(final int size) {
255                return new ResultMatcher() {
256                        @Override
257                        public void match(MvcResult result) throws Exception {
258                                ModelAndView mav = getModelAndView(result);
259                                int actual = 0;
260                                for (String key : mav.getModel().keySet()) {
261                                        if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
262                                                actual++;
263                                        }
264                                }
265                                assertEquals("Model size", size, actual);
266                        }
267                };
268        }
269
270        private ModelAndView getModelAndView(MvcResult mvcResult) {
271                ModelAndView mav = mvcResult.getModelAndView();
272                assertTrue("No ModelAndView found", mav != null);
273                return mav;
274        }
275
276        private BindingResult getBindingResult(ModelAndView mav, String name) {
277                BindingResult result = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name);
278                assertTrue("No BindingResult for attribute: " + name, result != null);
279                return result;
280        }
281
282        private int getErrorCount(ModelMap model) {
283                int count = 0;
284                for (Object value : model.values()) {
285                        if (value instanceof Errors) {
286                                count += ((Errors) value).getErrorCount();
287                        }
288                }
289                return count;
290        }
291
292}