001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau; 018 019import static org.apache.juneau.commons.utils.ThrowableUtils.*; 020import static org.apache.juneau.marshall.marshaller.MarshallUtils.*; 021import static org.apache.juneau.bct.BctAssertions.*; 022import static org.junit.jupiter.api.Assertions.*; 023 024import org.apache.juneau.bct.*; 025 026/** 027 * Utility class for testing bean functionality in a standardized way. 028 * 029 * <p> 030 * This class provides a fluent API for setting up and executing common bean tests including: 031 * <ul> 032 * <li>Getter/setter validation using {@link BctAssertions#assertBean(Object, String, String)} 033 * <li>Copy constructor/method testing 034 * <li>JSON serialization/deserialization testing 035 * <li>Round-trip serialization testing 036 * <li>toString() method validation 037 * </ul> 038 * 039 * <h5 class='section'>Example Usage:</h5> 040 * <p class='bjava'> 041 * BeanTester<MyBean> tester = testBean(myBean) 042 * .json("{prop1:'value1',prop2:'value2'}") 043 * .props("prop1,prop2") 044 * .vals("'value1','value2'") 045 * .string("{prop1:'value1',prop2:'value2'}"); 046 * 047 * tester.assertGettersAndSetters(); 048 * tester.assertToJson(); 049 * tester.assertFromJson(); 050 * tester.assertRoundTrip(); 051 * tester.assertToString(); 052 * </p> 053 * 054 * <p> 055 * This class is typically used in nested test classes to provide consistent testing patterns 056 * across different bean types in the Apache Juneau project. 057 * 058 * @param <T> The type of bean being tested 059 */ 060@SuppressWarnings({ 061 "java:S5960", // Test utility; JUnit assertions are its intentional purpose, not a production-code side effect. 062 "unchecked" // Class<T> cast for generic bean type in test utility 063}) 064public class BeanTester<T> { 065 066 private T bean; 067 private Class<T> c; 068 069 /** 070 * Sets the bean instance to be tested. 071 * 072 * @param value The bean instance to test. Must not be <jk>null</jk>. 073 * @return This object for method chaining. 074 */ 075 public BeanTester<T> bean(T value) { bean = value; c = (Class<T>) bean.getClass(); return this; } 076 077 /** 078 * Returns the bean instance being tested. 079 * 080 * @return The bean instance being tested. 081 */ 082 public T bean() { return bean; } 083 084 private String json; 085 086 /** 087 * Sets the expected JSON representation of the bean. 088 * 089 * <p> 090 * This JSON string is used by {@link #assertToJson()} to verify that the bean 091 * serializes to the expected JSON format, and by {@link #assertFromJson()} to 092 * verify that the JSON deserializes back to an equivalent bean. 093 * 094 * @param value The expected JSON representation. 095 * @return This object for method chaining. 096 */ 097 public BeanTester<T> json(String value) { json = value; return this; } 098 099 /** 100 * Returns the expected JSON representation of the bean. 101 * 102 * @return The expected JSON representation. 103 */ 104 public String json() { return json; } 105 106 private String props; 107 108 /** 109 * Sets the comma-delimited list of property names for bean validation. 110 * 111 * <p> 112 * This string is used by {@link BctAssertions#assertBean(Object, String, String)} to 113 * validate that the bean has the expected properties with the expected values. 114 * Property names should be listed in the same order as their corresponding values 115 * in the {@link #vals(String)} parameter. 116 * 117 * @param value Comma-delimited list of property names (e.g., "prop1,prop2,prop3"). 118 * @return This object for method chaining. 119 */ 120 public BeanTester<T> props(String value) { props = value; return this; } 121 122 /** 123 * Returns the comma-delimited list of property names. 124 * 125 * @return The comma-delimited list of property names. 126 */ 127 public String props() { return props; } 128 129 private String vals; 130 131 /** 132 * Sets the comma-delimited list of expected property values for bean validation. 133 * 134 * <p> 135 * This string is used by {@link BctAssertions#assertBean(Object, String, String)} to 136 * validate that the bean properties have the expected values. Values should be 137 * listed in the same order as their corresponding property names in the 138 * {@link #props(String)} parameter. String values should be quoted (e.g., "'value'"). 139 * 140 * @param value Comma-delimited list of expected values (e.g., "'value1','value2',123"). 141 * @return This object for method chaining. 142 */ 143 public BeanTester<T> vals(String value) { vals = value; return this; } 144 145 /** 146 * Returns the comma-delimited list of expected property values. 147 * 148 * @return The comma-delimited list of expected property values. 149 */ 150 public String vals() { return vals; } 151 152 private String string; 153 154 /** 155 * Sets the expected string representation of the bean. 156 * 157 * <p> 158 * This string is used by {@link #assertToString()} to verify that the bean's 159 * {@code toString()} method returns the expected value. 160 * 161 * @param value The expected string representation. 162 * @return This object for method chaining. 163 */ 164 public BeanTester<T> string(String value) { string = value; return this; } 165 166 /** 167 * Returns the expected string representation of the bean. 168 * 169 * @return The expected string representation. 170 */ 171 public String string() { return string; } 172 173 /** 174 * Validates that the bean's getters and setters work correctly. 175 * 176 * <p> 177 * This method uses {@link BctAssertions#assertBean(Object, String, String)} to verify 178 * that the bean's properties match the expected values specified by {@link #props(String)} 179 * and {@link #vals(String)}. 180 * 181 * @throws AssertionError if any property values don't match expectations. 182 */ 183 public void assertGettersAndSetters() { 184 assertBean(bean, props, vals); 185 } 186 187 /** 188 * Validates that the bean's copy constructor or copy() method works correctly. 189 * 190 * <p> 191 * This method uses reflection to invoke the bean's {@code copy()} method, then verifies: 192 * <ul> 193 * <li>The returned object is not the same instance as the original 194 * <li>The returned object has the same property values as the original 195 * </ul> 196 * 197 * @throws AssertionError if the copy is the same instance or has different property values. 198 * @throws RuntimeException if the bean doesn't have a {@code copy()} method or it fails to invoke. 199 */ 200 public void assertCopy() { 201 var t = safe(()->bean.getClass().getMethod("copy").invoke(bean)); 202 assertNotSame(bean, t); 203 assertBean(t, props, vals); 204 } 205 206 /** 207 * Validates that the bean serializes to the expected JSON format. 208 * 209 * <p> 210 * This method verifies that the bean serializes (via JSON5) to the JSON string 211 * specified by {@link #json(String)}. 212 * 213 * @throws AssertionError if the serialized JSON doesn't match the expected format. 214 */ 215 public void assertToJson() { 216 assertEquals(json, json5(bean)); 217 } 218 219 /** 220 * Validates that the expected JSON deserializes to a bean with correct property values. 221 * 222 * <p> 223 * This method deserializes the JSON string specified by {@link #json(String)} and 224 * verifies that the resulting bean has the expected property values specified by 225 * {@link #props(String)} and {@link #vals(String)}. 226 * 227 * @throws AssertionError if the deserialized bean doesn't have the expected property values. 228 * @throws RuntimeException if JSON deserialization fails. 229 */ 230 public void assertFromJson() { 231 assertBean(json(json, c), props, vals); 232 } 233 234 /** 235 * Validates that the bean can be serialized to JSON and deserialized back correctly. 236 * 237 * <p> 238 * This method performs a complete round-trip test by: 239 * <ol> 240 * <li>Serializing the bean to JSON 241 * <li>Deserializing the JSON back to a bean 242 * <li>Verifying the resulting bean has the expected property values 243 * </ol> 244 * 245 * @throws AssertionError if the round-trip bean doesn't have the expected property values. 246 * @throws RuntimeException if serialization or deserialization fails. 247 */ 248 public void assertRoundTrip() { 249 assertBean(jsonRoundTrip(bean, c), props, vals); 250 } 251 252 /** 253 * Validates that the bean's toString() method returns the expected string. 254 * 255 * <p> 256 * This method compares the bean's {@code toString()} output with the string 257 * specified by {@link #string(String)}. 258 * 259 * @throws AssertionError if the toString() output doesn't match the expected string. 260 */ 261 public void assertToString() { 262 assertEquals(string, bean.toString()); 263 } 264 265 /** 266 * Serializes an object to JSON using the default sorted JSON serializer. 267 * 268 * @param o The object to serialize. 269 * @return The JSON representation of the object. 270 */ 271 private static String json(Object o) { 272 return json5(o); 273 } 274 275 /** 276 * Deserializes a JSON string to an object of the specified type. 277 * 278 * @param <T> The type to deserialize to. 279 * @param o The JSON string to deserialize. 280 * @param c The class to deserialize to. 281 * @return The deserialized object. 282 */ 283 private static <T> T json(String o, Class<T> c) { 284 return safe(()->json5(o, c)); 285 } 286 287 /** 288 * Performs a round-trip serialization/deserialization test. 289 * 290 * @param <T> The type of object to round-trip. 291 * @param o The object to serialize and deserialize. 292 * @param c The class to deserialize to. 293 * @return The object after round-trip serialization/deserialization. 294 */ 295 private static <T> T jsonRoundTrip(T o, Class<T> c) { 296 return json(json(o), c); 297 } 298}