001/* 002 * Copyright 2023 the original author or authors. 003 * <p> 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 * <p> 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * <p> 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 */ 016package de.cuioss.test.valueobjects.property; 017 018import static org.junit.jupiter.api.Assertions.assertEquals; 019import static org.junit.jupiter.api.Assertions.assertNotNull; 020import static org.junit.jupiter.api.Assertions.assertTrue; 021 022import de.cuioss.test.generator.TypedGenerator; 023import de.cuioss.test.valueobjects.property.util.AssertionStrategy; 024import de.cuioss.test.valueobjects.property.util.CollectionAsserts; 025import de.cuioss.test.valueobjects.property.util.PropertyAccessStrategy; 026import lombok.Getter; 027import lombok.NonNull; 028import lombok.RequiredArgsConstructor; 029import lombok.Setter; 030import lombok.ToString; 031 032/** 033 * Stateful wrapper around instances of {@link PropertyMetadata} that provides 034 * methods for reading / asserting properties according to the configured 035 * {@link PropertyAccessStrategy}, see {@link #apply(Object)}, 036 * {@link #assertValueSet(Object)} and {@link #assertDefaultValue(Object)} . In 037 * addition it can create / store an instant specific generatedValue, see 038 * {@link #generateTestValue()} 039 * 040 * @author Oliver Wolff 041 */ 042@RequiredArgsConstructor 043@ToString 044public class PropertySupport { 045 046 /** 047 * Used in conjunction with {@link #createCopyWithNonEqualValue()}. Defines the 048 * upper bound of retries for generating non-equal values from a given 049 * {@link TypedGenerator} 050 */ 051 private static final int ENTROPY_GUARD = 50; 052 053 private static final String TARGET_MUST_NOT_BE_NULL = "target must not be null"; 054 055 @NonNull 056 @Getter 057 private final PropertyMetadata propertyMetadata; 058 059 @Getter 060 @Setter 061 private Object generatedValue; 062 063 // Shortcuts to PropertyMetadata 064 /** 065 * @return boolean indicating whether the current property is readable 066 */ 067 public boolean isReadable() { 068 return propertyMetadata.getPropertyReadWrite().isReadable(); 069 } 070 071 /** 072 * @return boolean indicating whether the current property is defaultValued 073 */ 074 public boolean isDefaultValue() { 075 return propertyMetadata.isDefaultValue(); 076 } 077 078 /** 079 * @return boolean indicating whether the current property is required 080 */ 081 public boolean isRequired() { 082 return propertyMetadata.isRequired(); 083 } 084 085 /** 086 * @return the name of the property 087 */ 088 public String getName() { 089 return propertyMetadata.getName(); 090 } 091 092 /** 093 * @return boolean indicating whether the current property is a primitive 094 */ 095 public boolean isPrimitive() { 096 return propertyMetadata.resolveActualClass().isPrimitive(); 097 } 098 099 /** 100 * Generates a value from the contained generator and sets it to 101 * {@link #setGeneratedValue(Object)}. 102 * 103 * @return the generated test value 104 */ 105 public Object generateTestValue() { 106 setGeneratedValue(propertyMetadata.next()); 107 return getGeneratedValue(); 108 } 109 110 /** 111 * Resets the generated value 112 */ 113 public void reset() { 114 setGeneratedValue(null); 115 } 116 117 /** 118 * Writes the property, previously initialized with {@link #generateTestValue()} 119 * to the given target, using the configured {@link PropertyAccessStrategy} 120 * 121 * @param target must not be null 122 */ 123 public void apply(final Object target) { 124 writeProperty(target, generatedValue); 125 } 126 127 /** 128 * Asserts that the value previously set by {@link #apply(Object)} is accessible 129 * by the corresponding read-method, e.g getProperty 130 * 131 * @param target must not be null 132 */ 133 public void assertValueSet(final Object target) { 134 assertValueSet(target, generatedValue); 135 } 136 137 /** 138 * Asserts that the expected value is accessible by the corresponding 139 * read-method, e.g getProperty 140 * 141 * @param target must not be null 142 * @param expected the expected content of the attribute 143 */ 144 public void assertValueSet(final Object target, Object expected) { 145 final var actual = readProperty(target); 146 147 if (AssertionStrategy.COLLECTION_IGNORE_ORDER.equals(propertyMetadata.getAssertionStrategy())) { 148 CollectionAsserts.assertListsAreEqualIgnoringOrder(propertyMetadata.getName(), expected, actual); 149 } else { 150 assertEquals(expected, actual, "Invalid content found for property " + propertyMetadata.getName() 151 + ", expected=" + expected + ", actual=" + actual); 152 } 153 154 } 155 156 /** 157 * Reads and returns the read property from the given target 158 * 159 * @param target to be read from, must not be null 160 * @return he read Property, may be {@code null} 161 */ 162 public Object readProperty(Object target) { 163 assertNotNull(target, TARGET_MUST_NOT_BE_NULL); 164 return propertyMetadata.getPropertyAccessStrategy().readProperty(target, propertyMetadata); 165 } 166 167 /** 168 * Writes the given property the given target 169 * 170 * @param target to be written to, must not be null 171 * @param propertyValue to be written 172 * @return he read Property, may be {@code null} 173 */ 174 public Object writeProperty(Object target, Object propertyValue) { 175 assertNotNull(target, TARGET_MUST_NOT_BE_NULL); 176 return propertyMetadata.getPropertyAccessStrategy().writeProperty(target, propertyMetadata, propertyValue); 177 } 178 179 /** 180 * Asserts that the given object provides a default value. 181 * 182 * @param target must not be null 183 */ 184 public void assertDefaultValue(final Object target) { 185 186 assertNotNull(target, TARGET_MUST_NOT_BE_NULL); 187 188 assertTrue(propertyMetadata.isDefaultValue(), 189 "There is no default value set: Invalid configuration for property " + propertyMetadata.getName()); 190 191 assertNotNull(readProperty(target), "No defaultValue found for property " + propertyMetadata.getName()); 192 } 193 194 /** 195 * Creates a copy of this instance 196 * 197 * @param withGeneratedValue indicating whether to copy the currently 198 * {@link #getGeneratedValue()} 199 * @return the created copy 200 */ 201 public PropertySupport createCopy(final boolean withGeneratedValue) { 202 final var support = new PropertySupport(propertyMetadata); 203 if (withGeneratedValue) { 204 support.setGeneratedValue(generatedValue); 205 } 206 return support; 207 } 208 209 /** 210 * Creates a copy of this instance. In addition it tries to create a 211 * generatedValue that is not equal to the contained one. It will try this 50 212 * times and will then throw an {@link AssertionError} 213 * 214 * @return the created copy 215 */ 216 public PropertySupport createCopyWithNonEqualValue() { 217 final var support = new PropertySupport(propertyMetadata); 218 if (null == getGeneratedValue()) { 219 support.generateTestValue(); 220 return support; 221 } 222 var times = 0; 223 final var initialTestValue = getGeneratedValue(); 224 while (true) { 225 final var otherValue = support.generateTestValue(); 226 if (!initialTestValue.equals(otherValue)) { 227 break; 228 } 229 times++; 230 if (ENTROPY_GUARD == times) { 231 throw new AssertionError("Unable to create non equal test-value for " + propertyMetadata); 232 } 233 } 234 return support; 235 } 236}