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}