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;
018
019import java.util.Comparator;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.springframework.util.ObjectUtils;
026import org.springframework.web.servlet.ModelAndView;
027
028import static org.springframework.test.util.AssertionErrors.assertTrue;
029import static org.springframework.test.util.AssertionErrors.fail;
030
031/**
032 * A collection of assertions intended to simplify testing scenarios dealing
033 * with Spring Web MVC {@link org.springframework.web.servlet.ModelAndView
034 * ModelAndView} objects.
035 *
036 * <p>Intended for use with JUnit 4 and TestNG. All {@code assert*()} methods
037 * throw {@link AssertionError AssertionErrors}.
038 *
039 * @author Sam Brannen
040 * @author Alef Arendsen
041 * @author Bram Smeets
042 * @since 2.5
043 * @see org.springframework.web.servlet.ModelAndView
044 */
045public abstract class ModelAndViewAssert {
046
047        /**
048         * Checks whether the model value under the given {@code modelName}
049         * exists and checks it type, based on the {@code expectedType}. If the
050         * model entry exists and the type matches, the model value is returned.
051         * @param mav the ModelAndView to test against (never {@code null})
052         * @param modelName name of the object to add to the model (never {@code null})
053         * @param expectedType expected type of the model value
054         * @return the model value
055         */
056        @SuppressWarnings("unchecked")
057        public static <T> T assertAndReturnModelAttributeOfType(ModelAndView mav, String modelName, Class<T> expectedType) {
058                Map<String, Object> model = mav.getModel();
059                Object obj = model.get(modelName);
060                if (obj == null) {
061                        fail("Model attribute with name '" + modelName + "' is null");
062                }
063                assertTrue("Model attribute is not of expected type '" + expectedType.getName() + "' but rather of type '" +
064                                obj.getClass().getName() + "'", expectedType.isAssignableFrom(obj.getClass()));
065                return (T) obj;
066        }
067
068        /**
069         * Compare each individual entry in a list, without first sorting the lists.
070         * @param mav the ModelAndView to test against (never {@code null})
071         * @param modelName name of the object to add to the model (never {@code null})
072         * @param expectedList the expected list
073         */
074        @SuppressWarnings("rawtypes")
075        public static void assertCompareListModelAttribute(ModelAndView mav, String modelName, List expectedList) {
076                List modelList = assertAndReturnModelAttributeOfType(mav, modelName, List.class);
077                assertTrue("Size of model list is '" + modelList.size() + "' while size of expected list is '" +
078                                expectedList.size() + "'", expectedList.size() == modelList.size());
079                assertTrue("List in model under name '" + modelName + "' is not equal to the expected list.",
080                                expectedList.equals(modelList));
081        }
082
083        /**
084         * Assert whether or not a model attribute is available.
085         * @param mav the ModelAndView to test against (never {@code null})
086         * @param modelName name of the object to add to the model (never {@code null})
087         */
088        public static void assertModelAttributeAvailable(ModelAndView mav, String modelName) {
089                Map<String, Object> model = mav.getModel();
090                assertTrue("Model attribute with name '" + modelName + "' is not available", model.containsKey(modelName));
091        }
092
093        /**
094         * Compare a given {@code expectedValue} to the value from the model
095         * bound under the given {@code modelName}.
096         * @param mav the ModelAndView to test against (never {@code null})
097         * @param modelName name of the object to add to the model (never {@code null})
098         * @param expectedValue the model value
099         */
100        public static void assertModelAttributeValue(ModelAndView mav, String modelName, Object expectedValue) {
101                Object modelValue = assertAndReturnModelAttributeOfType(mav, modelName, Object.class);
102                assertTrue("Model value with name '" + modelName + "' is not the same as the expected value which was '" +
103                                expectedValue + "'", modelValue.equals(expectedValue));
104        }
105
106        /**
107         * Inspect the {@code expectedModel} to see if all elements in the
108         * model appear and are equal.
109         * @param mav the ModelAndView to test against (never {@code null})
110         * @param expectedModel the expected model
111         */
112        public static void assertModelAttributeValues(ModelAndView mav, Map<String, Object> expectedModel) {
113                Map<String, Object> model = mav.getModel();
114
115                if (!model.keySet().equals(expectedModel.keySet())) {
116                        StringBuilder sb = new StringBuilder("Keyset of expected model does not match.\n");
117                        appendNonMatchingSetsErrorMessage(expectedModel.keySet(), model.keySet(), sb);
118                        fail(sb.toString());
119                }
120
121                StringBuilder sb = new StringBuilder();
122                model.forEach((modelName, mavValue) -> {
123                        Object assertionValue = expectedModel.get(modelName);
124                        if (!assertionValue.equals(mavValue)) {
125                                sb.append("Value under name '").append(modelName).append("' differs, should have been '").append(
126                                        assertionValue).append("' but was '").append(mavValue).append("'\n");
127                        }
128                });
129
130                if (sb.length() != 0) {
131                        sb.insert(0, "Values of expected model do not match.\n");
132                        fail(sb.toString());
133                }
134        }
135
136        /**
137         * Compare each individual entry in a list after having sorted both lists
138         * (optionally using a comparator).
139         * @param mav the ModelAndView to test against (never {@code null})
140         * @param modelName name of the object to add to the model (never {@code null})
141         * @param expectedList the expected list
142         * @param comparator the comparator to use (may be {@code null}). If not
143         * specifying the comparator, both lists will be sorted not using any comparator.
144         */
145        @SuppressWarnings({"rawtypes", "unchecked"})
146        public static void assertSortAndCompareListModelAttribute(
147                        ModelAndView mav, String modelName, List expectedList, Comparator comparator) {
148
149                List modelList = assertAndReturnModelAttributeOfType(mav, modelName, List.class);
150                assertTrue("Size of model list is '" + modelList.size() + "' while size of expected list is '" +
151                                expectedList.size() + "'", expectedList.size() == modelList.size());
152
153                modelList.sort(comparator);
154                expectedList.sort(comparator);
155
156                assertTrue("List in model under name '" + modelName + "' is not equal to the expected list.",
157                                expectedList.equals(modelList));
158        }
159
160        /**
161         * Check to see if the view name in the ModelAndView matches the given
162         * {@code expectedName}.
163         * @param mav the ModelAndView to test against (never {@code null})
164         * @param expectedName the name of the model value
165         */
166        public static void assertViewName(ModelAndView mav, String expectedName) {
167                assertTrue("View name is not equal to '" + expectedName + "' but was '" + mav.getViewName() + "'",
168                                ObjectUtils.nullSafeEquals(expectedName, mav.getViewName()));
169        }
170
171
172        private static void appendNonMatchingSetsErrorMessage(
173                        Set<String> assertionSet, Set<String> incorrectSet, StringBuilder sb) {
174
175                Set<String> tempSet = new HashSet<>(incorrectSet);
176                tempSet.removeAll(assertionSet);
177
178                if (!tempSet.isEmpty()) {
179                        sb.append("Set has too many elements:\n");
180                        for (Object element : tempSet) {
181                                sb.append('-');
182                                sb.append(element);
183                                sb.append('\n');
184                        }
185                }
186
187                tempSet = new HashSet<>(assertionSet);
188                tempSet.removeAll(incorrectSet);
189
190                if (!tempSet.isEmpty()) {
191                        sb.append("Set is missing elements:\n");
192                        for (Object element : tempSet) {
193                                sb.append('-');
194                                sb.append(element);
195                                sb.append('\n');
196                        }
197                }
198        }
199
200}