• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / javabean-tester / 3146

01 Jan 2026 01:12PM UTC coverage: 87.852% (-0.2%) from 88.028%
3146

Pull #1211

github

web-flow
Merge 3fa32cd9a into ba6455af4
Pull Request #1211: Update dependency nl.jqno.equalsverifier:equalsverifier to v4.3

272 of 315 branches covered (86.35%)

499 of 568 relevant lines covered (87.85%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.54
/src/main/java/com/codebox/bean/JavaBeanTesterWorker.java
1
/*
2
 * JavaBean Tester (https://github.com/hazendaz/javabean-tester)
3
 *
4
 * Copyright 2012-2025 Hazendaz.
5
 *
6
 * All rights reserved. This program and the accompanying materials
7
 * are made available under the terms of The Apache Software License,
8
 * Version 2.0 which accompanies this distribution, and is available at
9
 * http://www.apache.org/licenses/LICENSE-2.0.txt
10
 *
11
 * Contributors:
12
 *     CodeBox (Rob Dawson).
13
 *     Hazendaz (Jeremy Landis).
14
 */
15
package com.codebox.bean;
16

17
import com.codebox.enums.CheckClear;
18
import com.codebox.enums.CheckConstructor;
19
import com.codebox.enums.CheckEquals;
20
import com.codebox.enums.CheckSerialize;
21
import com.codebox.enums.LoadData;
22
import com.codebox.enums.LoadType;
23
import com.codebox.enums.SkipStrictSerialize;
24
import com.codebox.instance.ClassInstance;
25

26
import java.beans.IntrospectionException;
27
import java.beans.Introspector;
28
import java.beans.PropertyDescriptor;
29
import java.io.ByteArrayInputStream;
30
import java.io.ByteArrayOutputStream;
31
import java.io.Externalizable;
32
import java.io.IOException;
33
import java.io.ObjectInputStream;
34
import java.io.ObjectOutputStream;
35
import java.io.Serializable;
36
import java.lang.annotation.Annotation;
37
import java.lang.reflect.Constructor;
38
import java.lang.reflect.InvocationTargetException;
39
import java.lang.reflect.Method;
40
import java.lang.reflect.Modifier;
41
import java.util.ArrayList;
42
import java.util.Arrays;
43
import java.util.Date;
44
import java.util.HashSet;
45
import java.util.List;
46
import java.util.Set;
47

48
import lombok.Data;
49

50
import nl.jqno.equalsverifier.EqualsVerifier;
51
import nl.jqno.equalsverifier.Warning;
52

53
import org.junit.jupiter.api.Assertions;
54
import org.slf4j.Logger;
55
import org.slf4j.LoggerFactory;
56

57
/**
58
 * The Class JavaBeanTesterWorker.
59
 *
60
 * @param <T>
61
 *            the generic type
62
 * @param <E>
63
 *            the element type
64
 */
65
@Data
66
class JavaBeanTesterWorker<T, E> {
67

68
    /** The Constant logger. */
69
    private static final Logger logger = LoggerFactory.getLogger(JavaBeanTesterWorker.class);
1✔
70

71
    /** The check clear. */
72
    private CheckClear checkClear;
73

74
    /** The check constructor. */
75
    private CheckConstructor checkConstructor;
76

77
    /** The check equals. */
78
    private CheckEquals checkEquals;
79

80
    /** The check serializable. */
81
    private CheckSerialize checkSerializable;
82

83
    /** The load data. */
84
    private LoadData loadData;
85

86
    /** The clazz. */
87
    private final Class<T> clazz;
88

89
    /** The extension. */
90
    private Class<E> extension;
91

92
    /** The skip strict serialize. */
93
    private SkipStrictSerialize skipStrictSerializable;
94

95
    /** The skip these. */
96
    private Set<String> skipThese = new HashSet<>();
1✔
97

98
    /**
99
     * Instantiates a new java bean tester worker.
100
     *
101
     * @param newClazz
102
     *            the clazz
103
     */
104
    JavaBeanTesterWorker(final Class<T> newClazz) {
1✔
105
        this.clazz = newClazz;
1✔
106
    }
1✔
107

108
    /**
109
     * Instantiates a new java bean tester worker.
110
     *
111
     * @param newClazz
112
     *            the clazz
113
     * @param newExtension
114
     *            the extension
115
     */
116
    JavaBeanTesterWorker(final Class<T> newClazz, final Class<E> newExtension) {
1✔
117
        this.clazz = newClazz;
1✔
118
        this.extension = newExtension;
1✔
119
    }
1✔
120

121
    /**
122
     * Tests the load methods of the specified class.
123
     *
124
     * @param <L>
125
     *            the type parameter associated with the class under test.
126
     * @param clazz
127
     *            the class under test.
128
     * @param instance
129
     *            the instance of class under test.
130
     * @param loadData
131
     *            load recursively all underlying data objects.
132
     * @param skipThese
133
     *            the names of any properties that should not be tested.
134
     *
135
     * @return the java bean tester worker
136
     */
137
    public static <L> JavaBeanTesterWorker<L, Object> load(final Class<L> clazz, final L instance,
138
            final LoadData loadData, final String... skipThese) {
139
        final JavaBeanTesterWorker<L, Object> worker = new JavaBeanTesterWorker<>(clazz);
1✔
140

141
        worker.setLoadData(loadData);
1✔
142
        if (skipThese != null) {
1!
143
            worker.setSkipThese(new HashSet<>(Arrays.asList(skipThese)));
1✔
144
        }
145
        worker.getterSetterTests(instance);
1✔
146

147
        return worker;
1✔
148
    }
149

150
    /**
151
     * Tests the clear, get, set, equals, hashCode, toString, serializable, and constructor(s) methods of the specified
152
     * class.
153
     */
154
    public void test() {
155

156
        // Test Getter/Setter
157
        this.getterSetterTests(new ClassInstance<T>().newInstance(this.clazz));
1✔
158

159
        // Test Clear
160
        if (this.checkClear != CheckClear.OFF) {
1✔
161
            this.clearTest();
1✔
162
        }
163

164
        // Test constructor
165
        if (this.checkConstructor != CheckConstructor.OFF) {
1✔
166
            this.constructorsTest();
1✔
167
        }
168

169
        // Test Serializable (internally uses on/off/strict checks)
170
        this.checkSerializableTest();
1✔
171

172
        // Test Equals
173
        if (this.checkEquals == CheckEquals.ON) {
1✔
174
            this.equalsHashCodeToStringSymmetricTest();
1✔
175
        }
176

177
    }
1✔
178

179
    /**
180
     * Getter Setter Tests.
181
     *
182
     * @param instance
183
     *            the instance of class under test.
184
     *
185
     * @return the ter setter tests
186
     */
187
    void getterSetterTests(final T instance) {
188
        final PropertyDescriptor[] props = this.getProps(this.clazz);
1✔
189
        for (final PropertyDescriptor prop : props) {
1✔
190
            Method getter = prop.getReadMethod();
1✔
191
            final Method setter = prop.getWriteMethod();
1✔
192

193
            // Java Metro Bug Patch (Boolean Wrapper usage of 'is' possible
194
            if (getter == null && setter != null) {
1!
195
                final String isBooleanWrapper = "is" + setter.getName().substring(3);
1✔
196
                try {
197
                    getter = this.clazz.getMethod(isBooleanWrapper);
1✔
198
                } catch (NoSuchMethodException | SecurityException e) {
1✔
199
                    // Do nothing
200
                }
1✔
201
            }
202

203
            if (getter != null && setter != null) {
1✔
204
                // We have both a get and set method for this property
205
                final Class<?> returnType = getter.getReturnType();
1✔
206
                final Class<?>[] params = setter.getParameterTypes();
1✔
207

208
                if (params.length == 1 && params[0] == returnType) {
1!
209
                    // The set method has 1 argument, which is of the same type as the return type of the get method, so
210
                    // we can test this property
211
                    try {
212
                        // Build a value of the correct type to be passed to the set method
213
                        final Object value = this.buildValue(returnType, LoadType.STANDARD_DATA);
1✔
214

215
                        // Build an instance of the bean that we are testing (each property test gets a new instance)
216
                        final T bean = new ClassInstance<T>().newInstance(this.clazz);
1✔
217

218
                        // Call the set method, then check the same value comes back out of the get method
219
                        setter.invoke(bean, value);
1✔
220

221
                        // Use data set on instance
222
                        setter.invoke(instance, value);
1✔
223

224
                        final Object expectedValue = value;
1✔
225
                        Object actualValue = getter.invoke(bean);
1✔
226

227
                        // java.util.Date normalization patch
228
                        //
229
                        // Date is zero based so it adds 1 through normalization. Since we always pass '1' here, it is
230
                        // the same as stating February. Thus we roll over the month quite often into March towards
231
                        // end of the month resulting in '1' != '2' situation. The reason we pass '1' is that we are
232
                        // testing the content of the object and have no idea it is a date to start with. It is simply
233
                        // that it sees getters/setters and tries to load them appropriately. The underlying problem
234
                        // with that is that the Date object performs normalization to avoid dates like 2-30 that do
235
                        // not exist and is not a typical getter/setter use-case. It is also deprecated but we don't
236
                        // want to simply skip all deprecated items as we intend to test as much as possible.
237
                        //
238
                        if (this.clazz == Date.class && prop.getName().equals("month")
1✔
239
                                && expectedValue.equals(Integer.valueOf("1"))
1!
240
                                && actualValue.equals(Integer.valueOf("2"))) {
1!
241
                            actualValue = Integer.valueOf("1");
×
242
                        }
243

244
                        Assertions.assertEquals(expectedValue, actualValue,
1✔
245
                                String.format("Failed while testing property '%s' of class '%s'", prop.getName(),
1✔
246
                                        this.clazz.getName()));
1✔
247

248
                    } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException
×
249
                            | SecurityException e) {
250
                        Assertions.fail(String.format(
×
251
                                "An exception was thrown while testing class '%s' with the property (getter/setter) '%s': '%s'",
252
                                this.clazz.getName(), prop.getName(), e.toString()));
×
253
                    }
1✔
254
                }
255
            }
256
        }
257
    }
1✔
258

259
    /**
260
     * Clear test.
261
     */
262
    void clearTest() {
263
        final Method[] methods = this.clazz.getDeclaredMethods();
1✔
264
        for (final Method method : methods) {
1✔
265
            if (method.getName().equals("clear")) {
1✔
266
                final T newClass = new ClassInstance<T>().newInstance(this.clazz);
1✔
267
                final T expectedClass = new ClassInstance<T>().newInstance(this.clazz);
1✔
268
                try {
269
                    // Perform any Post Construction on object without parameters
270
                    List<Annotation> annotations = null;
1✔
271
                    for (final Method mt : methods) {
1✔
272
                        annotations = Arrays.asList(mt.getAnnotations());
1✔
273
                        for (final Annotation annotation : annotations) {
1✔
274
                            // XXX On purpose logic change to support both javax and jakarta namespace for annotations
275
                            if ("PostConstruct".equals(annotation.annotationType().getSimpleName())
1!
276
                                    && mt.getParameterTypes().length == 0) {
1!
277
                                // Invoke method newClass
278
                                mt.invoke(newClass);
1✔
279
                                // Invoke method expectedClass
280
                                mt.invoke(expectedClass);
1✔
281
                            }
282
                        }
1✔
283
                    }
284
                    // Invoke clear only on newClass
285
                    newClass.getClass().getMethod("clear").invoke(newClass);
1✔
286
                    Assertions.assertEquals(expectedClass, newClass,
1✔
287
                            String.format("Clear method does not match new object '%s'", this.clazz));
1✔
288
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
×
289
                        | NoSuchMethodException | SecurityException e) {
290
                    Assertions.fail(String.format("An exception was thrown while testing the Clear method '%s' : '%s'",
×
291
                            this.clazz.getName(), e.toString()));
×
292
                }
1✔
293
            }
294
        }
295
    }
1✔
296

297
    /**
298
     * Constructors test.
299
     */
300
    void constructorsTest() {
301
        for (final Constructor<?> constructor : this.clazz.getConstructors()) {
1✔
302

303
            // Skip deprecated constructors
304
            if (constructor.isAnnotationPresent(Deprecated.class)) {
1✔
305
                continue;
1✔
306
            }
307

308
            final Class<?>[] types = constructor.getParameterTypes();
1✔
309

310
            final Object[] values = new Object[constructor.getParameterTypes().length];
1✔
311

312
            // Load Data
313
            for (int i = 0; i < values.length; i++) {
1✔
314
                values[i] = this.buildValue(types[i], LoadType.STANDARD_DATA);
1✔
315
            }
316

317
            try {
318
                constructor.newInstance(values);
1✔
319
            } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
×
320
                Assertions.fail(
×
321
                        String.format("An exception was thrown while testing the constructor(s) '%s' with '%s': '%s'",
×
322
                                constructor.getName(), Arrays.toString(values), e.toString()));
×
323
            }
1✔
324

325
            // TODO 1/12/2019 JWL Add checking of new object properties
326
        }
327
    }
1✔
328

329
    /**
330
     * Check Serializable test.
331
     */
332
    void checkSerializableTest() {
333
        final T object = new ClassInstance<T>().newInstance(this.clazz);
1✔
334
        if (this.implementsSerializable(object)) {
1✔
335
            final T newObject = this.canSerialize(object);
1✔
336
            // Toggle to throw or not throw error with only one way working
337
            if (this.skipStrictSerializable != SkipStrictSerialize.ON) {
1✔
338
                Assertions.assertEquals(object, newObject);
1✔
339
            } else {
340
                Assertions.assertNotEquals(object, newObject);
1✔
341
            }
342
            return;
1✔
343
        }
344

345
        // Only throw error when specifically checking on serialization
346
        if (this.checkSerializable == CheckSerialize.ON) {
1!
347
            Assertions.fail(String.format("Class is not serializable '%s'", object.getClass().getName()));
×
348
        }
349
    }
1✔
350

351
    /**
352
     * Implements serializable.
353
     *
354
     * @param object
355
     *            the object
356
     *
357
     * @return true, if successful
358
     */
359
    boolean implementsSerializable(final T object) {
360
        return object instanceof Externalizable || object instanceof Serializable;
1!
361
    }
362

363
    /**
364
     * Can serialize.
365
     *
366
     * @param object
367
     *            the object
368
     *
369
     * @return object read after serialization
370
     */
371
    @SuppressWarnings("unchecked")
372
    T canSerialize(final T object) {
373
        byte[] byteArray = null;
1✔
374

375
        // Serialize data
376
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
1✔
377
                ObjectOutputStream oos = new ObjectOutputStream(baos)) {
1✔
378
            oos.writeObject(object);
1✔
379
            byteArray = baos.toByteArray();
1✔
380
        } catch (final IOException e) {
×
381
            Assertions.fail(String.format("An exception was thrown while serializing the class '%s': '%s',",
×
382
                    object.getClass().getName(), e.toString()));
×
383
            return null;
×
384
        }
1✔
385

386
        // Deserialize Data
387
        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
1✔
388
                ObjectInputStream ois = new ObjectInputStream(bais)) {
1✔
389
            return (T) ois.readObject();
1✔
390
        } catch (final ClassNotFoundException | IOException e) {
×
391
            Assertions.fail(String.format("An exception was thrown while deserializing the class '%s': '%s',",
×
392
                    object.getClass().getName(), e.toString()));
×
393
        }
394
        return null;
×
395
    }
396

397
    /**
398
     * Builds the value.
399
     *
400
     * @param <R>
401
     *            the generic type
402
     * @param returnType
403
     *            the return type
404
     * @param loadType
405
     *            the load type
406
     *
407
     * @return the object
408
     */
409
    private <R> Object buildValue(final Class<R> returnType, final LoadType loadType) {
410
        final ValueBuilder valueBuilder = new ValueBuilder();
1✔
411
        valueBuilder.setLoadData(this.loadData);
1✔
412
        return valueBuilder.buildValue(returnType, loadType);
1✔
413
    }
414

415
    /**
416
     * Tests the equals/hashCode/toString methods of the specified class.
417
     */
418
    public void equalsHashCodeToStringSymmetricTest() {
419
        // Run Equals Verifier
420
        try {
421
            EqualsVerifier.simple().forClass(this.clazz).suppress(Warning.BIGDECIMAL_EQUALITY).verify();
1✔
422
        } catch (AssertionError e) {
×
423
            JavaBeanTesterWorker.logger.warn("EqualsVerifier attempt failed: {}", e.getMessage());
×
424
        }
1✔
425

426
        // Create Instances
427
        final T x = new ClassInstance<T>().newInstance(this.clazz);
1✔
428
        final T y = new ClassInstance<T>().newInstance(this.clazz);
1✔
429

430
        Assertions.assertNotNull(x,
1✔
431
                String.format("Create new instance of class '%s' resulted in null", this.clazz.getName()));
1✔
432
        Assertions.assertNotNull(y,
1✔
433
                String.format("Create new instance of class '%s' resulted in null", this.clazz.getName()));
1✔
434

435
        // TODO 1/12/2019 JWL Internalize extension will require canEquals, equals, hashcode, and toString overrides.
436
        /*
437
         * try { this.extension = (Class<E>) new ExtensionBuilder<T>().generate(this.clazz); } catch (NotFoundException
438
         * e) { Assert.fail(e.getMessage()); } catch (CannotCompileException e) { Assert.fail(e.getMessage()); }
439
         */
440
        final E ext = new ClassInstance<E>().newInstance(this.extension);
1✔
441

442
        Assertions.assertNotNull(ext,
1✔
443
                String.format("Create new instance of extension %s resulted in null", this.extension.getName()));
1✔
444

445
        // Test Equals, HashCode, and ToString on Empty Objects
446
        Assertions.assertEquals(x, y,
1✔
447
                String.format(".equals() should be consistent for two empty objects of type %s", this.clazz.getName()));
1✔
448
        Assertions.assertEquals(x.hashCode(), y.hashCode(), String
1✔
449
                .format(".hashCode() should be consistent for two empty objects of type %s", this.clazz.getName()));
1✔
450
        Assertions.assertEquals(x.toString(), y.toString(), String
1✔
451
                .format(".toString() should be consistent for two empty objects of type %s", this.clazz.getName()));
1✔
452

453
        // Test Extension Equals, HashCode, and ToString on Empty Objects
454
        Assertions.assertNotEquals(ext, y,
1✔
455
                String.format(".equals() should not be equal for extension of type %s and empty object of type %s",
1✔
456
                        this.extension.getName(), this.clazz.getName()));
1✔
457
        Assertions.assertNotEquals(ext.hashCode(), y.hashCode(),
1✔
458
                String.format(".hashCode() should not be equal for extension of type %s and empty object of type %s",
1✔
459
                        this.extension.getName(), this.clazz.getName()));
1✔
460
        Assertions.assertNotEquals(ext.toString(), y.toString(),
1✔
461
                String.format(".toString() should not be equal for extension of type %s and empty object of type %s",
1✔
462
                        this.extension.getName(), this.clazz.getName()));
1✔
463

464
        // Test One Sided Tests on Empty Objects
465
        Assertions.assertNotEquals(x, null,
1✔
466
                String.format("An empty object of type %s should not be equal to null", this.clazz.getName()));
1✔
467
        Assertions.assertEquals(x, x,
1✔
468
                String.format("An empty object of type %s should be equal to itself", this.clazz.getName()));
1✔
469

470
        // Test Extension One Sided Tests on Empty Objects
471
        Assertions.assertNotEquals(ext, null,
1✔
472
                String.format("An empty extension of type %s should not be equal to null", this.clazz.getName()));
1✔
473
        Assertions.assertEquals(ext, ext,
1✔
474
                String.format("An empty extension of type %s should be equal to itself", this.extension.getName()));
1✔
475

476
        // If the class has setters, the previous tests would have been against empty classes
477
        // If so, load the classes and re-test
478
        if (this.classHasSetters(this.clazz)) {
1✔
479
            // Populate Side X
480
            JavaBeanTesterWorker.load(this.clazz, x, this.loadData);
1✔
481

482
            // Populate Extension Side Ext
483
            JavaBeanTesterWorker.load(this.extension, ext, this.loadData);
1✔
484

485
            // ReTest Equals (flip)
486
            Assertions.assertNotEquals(y, x,
1✔
487
                    String.format(".equals() should not be consistent for one empty and one loaded object of type %s",
1✔
488
                            this.clazz.getName()));
1✔
489

490
            // ReTest Extension Equals (flip)
491
            Assertions.assertNotEquals(y, ext,
1✔
492
                    String.format(".equals() should not be equal for extension of type %s and empty object of type %s",
1✔
493
                            this.extension.getName(), this.clazz.getName()));
1✔
494

495
            // Populate Size Y
496
            JavaBeanTesterWorker.load(this.clazz, y, this.loadData);
1✔
497

498
            // ReTest Equals and HashCode
499
            if (this.loadData == LoadData.ON) {
1✔
500
                Assertions.assertEquals(x, y,
1✔
501
                        String.format(".equals() should be equal for two instances of type %s with loaded data",
1✔
502
                                this.clazz.getName()));
1✔
503
                Assertions.assertEquals(x.hashCode(), y.hashCode(),
1✔
504
                        String.format(".hashCode() should be equal for two instances of type %s with loaded data",
1✔
505
                                this.clazz.getName()));
1✔
506
            } else {
507
                Assertions.assertNotEquals(x, y);
1✔
508
                Assertions.assertNotEquals(x.hashCode(), y.hashCode());
1✔
509
            }
510

511
            // ReTest Extension Equals, HashCode, and ToString
512
            Assertions.assertNotEquals(ext, y,
1✔
513
                    String.format(".equals() should not be equal for extension of type %s and empty object of type %s",
1✔
514
                            this.extension.getName(), this.clazz.getName()));
1✔
515
            Assertions.assertNotEquals(ext.hashCode(), y.hashCode(),
1✔
516
                    String.format(
1✔
517
                            ".hashCode() should not be equal for extension of type %s and empty object of type %s",
518
                            this.extension.getName(), this.clazz.getName()));
1✔
519
            Assertions.assertNotEquals(ext.toString(), y.toString(),
1✔
520
                    String.format(
1✔
521
                            ".toString() should not be equal for extension of type %s and empty object of type %s",
522
                            this.extension.getName(), this.clazz.getName()));
1✔
523
        }
524

525
        // Create Immutable Instance
526
        try {
527
            final T e = new ClassInstance<T>().newInstance(this.clazz);
1✔
528
            ByteBuddyBeanCopier.copy(x, e, (value, targetType) -> {
1✔
529
                if (targetType == boolean.class) {
1✔
530
                    return value == null ? Boolean.FALSE : value;
1!
531
                }
532
                return value;
1✔
533
            });
534
            Assertions.assertEquals(e, x);
1✔
535
        } catch (final Exception e) {
×
536
            JavaBeanTesterWorker.logger.trace("Do nothing class is not mutable", e);
×
537
        }
1✔
538

539
        // If class is final, use Object.class for comparison needs
540
        if (Modifier.isFinal(clazz.getModifiers())) {
1✔
541
            JavaBeanTesterWorker.logger.trace("Final object does not go through final equals check");
1✔
542
            return;
1✔
543
        }
544

545
        // Create Extension Immutable Instance
546
        try {
547
            final E e = new ClassInstance<E>().newInstance(this.extension);
1✔
548
            ByteBuddyBeanCopier.copy(ext, e, (value, targetType) -> {
1✔
549
                if (targetType == boolean.class) {
1✔
550
                    return value == null ? Boolean.FALSE : value;
1!
551
                }
552
                return value;
1✔
553
            });
554
            Assertions.assertEquals(e, ext);
1✔
555
        } catch (final Exception e) {
×
556
            JavaBeanTesterWorker.logger.trace("Do nothing class is not mutable", e);
×
557
        }
1✔
558
    }
1✔
559

560
    /**
561
     * Equals Tests will traverse one object changing values until all have been tested against another object. This is
562
     * done to effectively test all paths through equals.
563
     *
564
     * @param instance
565
     *            the class instance under test.
566
     * @param expected
567
     *            the instance expected for tests.
568
     */
569
    void equalsTests(final T instance, final T expected) {
570

571
        // Perform hashCode test dependent on data coming in
572
        // Assert.assertEquals(expected.hashCode(), instance.hashCode());
573
        if (expected.hashCode() == instance.hashCode()) {
1✔
574
            Assertions.assertEquals(expected.hashCode(), instance.hashCode());
1✔
575
        } else {
576
            Assertions.assertNotEquals(expected.hashCode(), instance.hashCode());
1✔
577
        }
578

579
        final ValueBuilder valueBuilder = new ValueBuilder();
1✔
580
        valueBuilder.setLoadData(this.loadData);
1✔
581

582
        final PropertyDescriptor[] props = this.getProps(instance.getClass());
1✔
583
        for (final PropertyDescriptor prop : props) {
1✔
584
            Method getter = prop.getReadMethod();
1✔
585
            final Method setter = prop.getWriteMethod();
1✔
586

587
            // Java Metro Bug Patch (Boolean Wrapper usage of 'is' possible
588
            if (getter == null && setter != null) {
1!
589
                final String isBooleanWrapper = "is" + setter.getName().substring(3);
1✔
590
                try {
591
                    getter = this.clazz.getMethod(isBooleanWrapper);
1✔
592
                } catch (NoSuchMethodException | SecurityException e) {
×
593
                    // Do nothing
594
                }
1✔
595
            }
596

597
            if (getter != null && setter != null) {
1!
598
                // We have both a get and set method for this property
599
                final Class<?> returnType = getter.getReturnType();
1✔
600
                final Class<?>[] params = setter.getParameterTypes();
1✔
601

602
                if (params.length == 1 && params[0] == returnType) {
1!
603
                    // The set method has 1 argument, which is of the same type as the return type of the get method, so
604
                    // we can test this property
605
                    try {
606
                        // Save original value
607
                        final Object original = getter.invoke(instance);
1✔
608

609
                        // Build a value of the correct type to be passed to the set method using alternate test
610
                        Object value = valueBuilder.buildValue(returnType, LoadType.ALTERNATE_DATA);
1✔
611

612
                        // Call the set method, then check the same value comes back out of the get method
613
                        setter.invoke(instance, value);
1✔
614

615
                        // Check equals depending on data
616
                        if (instance.equals(expected)) {
1✔
617
                            Assertions.assertEquals(expected, instance);
1✔
618
                        } else {
619
                            Assertions.assertNotEquals(expected, instance);
1✔
620
                        }
621

622
                        // Build a value of the correct type to be passed to the set method using null test
623
                        value = valueBuilder.buildValue(returnType, LoadType.NULL_DATA);
1✔
624

625
                        // Call the set method, then check the same value comes back out of the get method
626
                        setter.invoke(instance, value);
1✔
627

628
                        // Check equals depending on data
629
                        if (instance.equals(expected)) {
1✔
630
                            Assertions.assertEquals(expected, instance);
1✔
631
                        } else {
632
                            Assertions.assertNotEquals(expected, instance);
1✔
633
                        }
634

635
                        // Reset to original value
636
                        setter.invoke(instance, original);
1✔
637

638
                    } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException
×
639
                            | SecurityException e) {
640
                        Assertions.fail(
×
641
                                String.format("An exception was thrown while testing the property (equals) '%s': '%s'",
×
642
                                        prop.getName(), e.toString()));
×
643
                    }
1✔
644
                }
645
            }
646
        }
647
    }
1✔
648

649
    /**
650
     * Class has setters.
651
     *
652
     * @param clazz
653
     *            the clazz
654
     *
655
     * @return true, if successful
656
     */
657
    private boolean classHasSetters(final Class<T> clazz) {
658
        return Arrays.stream(this.getProps(clazz))
1✔
659
                .anyMatch(propertyDescriptor -> propertyDescriptor.getWriteMethod() != null);
1✔
660
    }
661

662
    /**
663
     * Gets the props.
664
     *
665
     * @param clazz
666
     *            the clazz
667
     *
668
     * @return the props
669
     */
670
    private PropertyDescriptor[] getProps(final Class<?> clazz) {
671
        try {
672
            final List<PropertyDescriptor> usedProps = new ArrayList<>(
1✔
673
                    Introspector.getBeanInfo(clazz).getPropertyDescriptors().length);
1✔
674
            final List<PropertyDescriptor> props = Arrays
1✔
675
                    .asList(Introspector.getBeanInfo(clazz).getPropertyDescriptors());
1✔
676
            for (final PropertyDescriptor prop : props) {
1✔
677
                // Check the list of properties that we don't want to test
678
                if (this.skipThese.contains(prop.getName())) {
1✔
679
                    continue;
1✔
680
                }
681
                usedProps.add(prop);
1✔
682
            }
1✔
683
            return usedProps.toArray(new PropertyDescriptor[0]);
1✔
684
        } catch (final IntrospectionException e) {
×
685
            Assertions.fail(String.format("An exception was thrown while testing class '%s': '%s'",
×
686
                    this.clazz.getName(), e.toString()));
×
687
            return new PropertyDescriptor[0];
×
688
        }
689
    }
690

691
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc