001/*
002 * Copyright 2002-2018 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.Collections;
020import java.util.Comparator;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.springframework.util.ObjectUtils;
027import org.springframework.web.servlet.ModelAndView;
028
029import static org.springframework.test.util.AssertionErrors.*;
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}s.
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 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                assertTrue("ModelAndView is null", mav != null);
059                Object obj = mav.getModel().get(modelName);
060                assertTrue("Model attribute with name '" + modelName + "' is null", obj != null);
061                assertTrue("Model attribute is not of expected type '" + expectedType.getName() + "' but rather of type '" +
062                                obj.getClass().getName() + "'", expectedType.isAssignableFrom(obj.getClass()));
063                return (T) obj;
064        }
065
066        /**
067         * Compare each individual entry in a list, without first sorting the lists.
068         * @param mav ModelAndView to test against (never {@code null})
069         * @param modelName name of the object to add to the model (never {@code null})
070         * @param expectedList the expected list
071         */
072        @SuppressWarnings("rawtypes")
073        public static void assertCompareListModelAttribute(ModelAndView mav, String modelName, List expectedList) {
074                assertTrue("ModelAndView is null", mav != null);
075                List modelList = assertAndReturnModelAttributeOfType(mav, modelName, List.class);
076                assertTrue("Size of model list is '" + modelList.size() + "' while size of expected list is '" +
077                                expectedList.size() + "'", expectedList.size() == modelList.size());
078                assertTrue("List in model under name '" + modelName + "' is not equal to the expected list.",
079                                expectedList.equals(modelList));
080        }
081
082        /**
083         * Assert whether or not a model attribute is available.
084         * @param mav ModelAndView to test against (never {@code null})
085         * @param modelName name of the object to add to the model (never {@code null})
086         */
087        public static void assertModelAttributeAvailable(ModelAndView mav, String modelName) {
088                assertTrue("ModelAndView is null", mav != null);
089                assertTrue("Model attribute with name '" + modelName + "' is not available",
090                                mav.getModel().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 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                assertTrue("ModelAndView is null", mav != null);
102                Object modelValue = assertAndReturnModelAttributeOfType(mav, modelName, Object.class);
103                assertTrue("Model value with name '" + modelName + "' is not the same as the expected value which was '" +
104                                expectedValue + "'", modelValue.equals(expectedValue));
105        }
106
107        /**
108         * Inspect the {@code expectedModel} to see if all elements in the
109         * model appear and are equal.
110         * @param mav ModelAndView to test against (never {@code null})
111         * @param expectedModel the expected model
112         */
113        public static void assertModelAttributeValues(ModelAndView mav, Map<String, Object> expectedModel) {
114                assertTrue("ModelAndView is null", mav != null);
115                Map<String, Object> model = mav.getModel();
116
117                if (!model.keySet().equals(expectedModel.keySet())) {
118                        StringBuilder sb = new StringBuilder("Keyset of expected model does not match.\n");
119                        appendNonMatchingSetsErrorMessage(expectedModel.keySet(), model.keySet(), sb);
120                        fail(sb.toString());
121                }
122
123                StringBuilder sb = new StringBuilder();
124                for (String modelName : model.keySet()) {
125                        Object assertionValue = expectedModel.get(modelName);
126                        Object mavValue = model.get(modelName);
127                        if (!assertionValue.equals(mavValue)) {
128                                sb.append("Value under name '").append(modelName).append("' differs, should have been '").append(
129                                        assertionValue).append("' but was '").append(mavValue).append("'\n");
130                        }
131                }
132
133                if (sb.length() != 0) {
134                        sb.insert(0, "Values of expected model do not match.\n");
135                        fail(sb.toString());
136                }
137        }
138
139        /**
140         * Compare each individual entry in a list after having sorted both lists
141         * (optionally using a comparator).
142         * @param mav ModelAndView to test against (never {@code null})
143         * @param modelName name of the object to add to the model (never {@code null})
144         * @param expectedList the expected list
145         * @param comparator the comparator to use (may be {@code null}). If not
146         * specifying the comparator, both lists will be sorted not using any comparator.
147         */
148        @SuppressWarnings({"unchecked", "rawtypes"})
149        public static void assertSortAndCompareListModelAttribute(
150                        ModelAndView mav, String modelName, List expectedList, Comparator comparator) {
151
152                assertTrue("ModelAndView is null", mav != null);
153                List modelList = assertAndReturnModelAttributeOfType(mav, modelName, List.class);
154                assertTrue("Size of model list is '" + modelList.size() + "' while size of expected list is '" +
155                                expectedList.size() + "'", expectedList.size() == modelList.size());
156
157                if (comparator != null) {
158                        Collections.sort(modelList, comparator);
159                        Collections.sort(expectedList, comparator);
160                }
161                else {
162                        Collections.sort(modelList);
163                        Collections.sort(expectedList);
164                }
165
166                assertTrue("List in model under name '" + modelName + "' is not equal to the expected list.",
167                                expectedList.equals(modelList));
168        }
169
170        /**
171         * Check to see if the view name in the ModelAndView matches the given
172         * {@code expectedName}.
173         * @param mav ModelAndView to test against (never {@code null})
174         * @param expectedName the name of the model value
175         */
176        public static void assertViewName(ModelAndView mav, String expectedName) {
177                assertTrue("ModelAndView is null", mav != null);
178                assertTrue("View name is not equal to '" + expectedName + "' but was '" + mav.getViewName() + "'",
179                                ObjectUtils.nullSafeEquals(expectedName, mav.getViewName()));
180        }
181
182
183        private static void appendNonMatchingSetsErrorMessage(
184                        Set<String> assertionSet, Set<String> incorrectSet, StringBuilder sb) {
185
186                Set<String> tempSet = new HashSet<String>(incorrectSet);
187                tempSet.removeAll(assertionSet);
188
189                if (!tempSet.isEmpty()) {
190                        sb.append("Set has too many elements:\n");
191                        for (Object element : tempSet) {
192                                sb.append('-');
193                                sb.append(element);
194                                sb.append('\n');
195                        }
196                }
197
198                tempSet = new HashSet<String>(assertionSet);
199                tempSet.removeAll(incorrectSet);
200
201                if (!tempSet.isEmpty()) {
202                        sb.append("Set is missing elements:\n");
203                        for (Object element : tempSet) {
204                                sb.append('-');
205                                sb.append(element);
206                                sb.append('\n');
207                        }
208                }
209        }
210
211}