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

hazendaz / javabean-tester / 3330

04 Apr 2026 08:39PM UTC coverage: 90.15% (-0.2%) from 90.317%
3330

push

github

web-flow
Merge pull request #1257 from hazendaz/renovate/byte-buddy.version

Update byte-buddy.version to v1.18.8-jdk5

286 of 331 branches covered (86.4%)

540 of 599 relevant lines covered (90.15%)

0.9 hits per line

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

87.13
/src/main/java/com/codebox/bean/JavaBeanTesterWorker.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2012-2026 hazendaz
6
 *
7
 * Portions of initial baseline code (getter/setter test) by Rob Dawson (CodeBox)
8
 */
9
package com.codebox.bean;
10

11
import com.codebox.enums.CheckClear;
12
import com.codebox.enums.CheckConstructor;
13
import com.codebox.enums.CheckEquals;
14
import com.codebox.enums.CheckSerialize;
15
import com.codebox.enums.LoadData;
16
import com.codebox.enums.LoadType;
17
import com.codebox.enums.SkipStrictSerialize;
18
import com.codebox.instance.ClassInstance;
19

20
import java.beans.IntrospectionException;
21
import java.beans.Introspector;
22
import java.beans.PropertyDescriptor;
23
import java.io.ByteArrayInputStream;
24
import java.io.ByteArrayOutputStream;
25
import java.io.Externalizable;
26
import java.io.IOException;
27
import java.io.ObjectInputStream;
28
import java.io.ObjectOutputStream;
29
import java.io.Serializable;
30
import java.lang.annotation.Annotation;
31
import java.lang.reflect.Constructor;
32
import java.lang.reflect.InvocationTargetException;
33
import java.lang.reflect.Method;
34
import java.lang.reflect.Modifier;
35
import java.util.ArrayList;
36
import java.util.Arrays;
37
import java.util.Date;
38
import java.util.HashSet;
39
import java.util.List;
40
import java.util.Set;
41

42
import lombok.Data;
43

44
import nl.jqno.equalsverifier.EqualsVerifier;
45
import nl.jqno.equalsverifier.Warning;
46

47
import org.junit.jupiter.api.Assertions;
48
import org.slf4j.Logger;
49
import org.slf4j.LoggerFactory;
50

51
/**
52
 * The Class JavaBeanTesterWorker.
53
 *
54
 * @param <T>
55
 *            the generic type
56
 * @param <E>
57
 *            the element type
58
 */
59
@Data
60
class JavaBeanTesterWorker<T, E> {
61

62
    /** The Constant logger. */
63
    private static final Logger logger = LoggerFactory.getLogger(JavaBeanTesterWorker.class);
1✔
64

65
    /** The check clear. */
66
    private CheckClear checkClear;
67

68
    /** The check constructor. */
69
    private CheckConstructor checkConstructor;
70

71
    /** The check equals. */
72
    private CheckEquals checkEquals;
73

74
    /** The check serializable. */
75
    private CheckSerialize checkSerializable;
76

77
    /** The load data. */
78
    private LoadData loadData;
79

80
    /** The clazz. */
81
    private final Class<T> clazz;
82

83
    /** The extension. */
84
    private Class<E> extension;
85

86
    /** The skip strict serialize. */
87
    private SkipStrictSerialize skipStrictSerializable;
88

89
    /** The skip these. */
90
    private Set<String> skipThese = new HashSet<>();
1✔
91

92
    /**
93
     * Instantiates a new java bean tester worker.
94
     *
95
     * @param newClazz
96
     *            the clazz
97
     */
98
    JavaBeanTesterWorker(final Class<T> newClazz) {
1✔
99
        this.clazz = newClazz;
1✔
100
    }
1✔
101

102
    /**
103
     * Instantiates a new java bean tester worker.
104
     *
105
     * @param newClazz
106
     *            the clazz
107
     * @param newExtension
108
     *            the extension
109
     */
110
    JavaBeanTesterWorker(final Class<T> newClazz, final Class<E> newExtension) {
1✔
111
        this.clazz = newClazz;
1✔
112
        this.extension = newExtension;
1✔
113
    }
1✔
114

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

135
        worker.setLoadData(loadData);
1✔
136
        if (skipThese != null) {
1!
137
            worker.setSkipThese(new HashSet<>(Arrays.asList(skipThese)));
1✔
138
        }
139
        worker.getterSetterTests(instance);
1✔
140

141
        return worker;
1✔
142
    }
143

144
    /**
145
     * Tests the clear, get, set, equals, hashCode, toString, serializable, and constructor(s) methods of the specified
146
     * class.
147
     */
148
    public void test() {
149

150
        // Test Getter/Setter
151
        this.getterSetterTests(new ClassInstance<T>().newInstance(this.clazz));
1✔
152

153
        // Test Clear
154
        if (this.checkClear != CheckClear.OFF) {
1✔
155
            this.clearTest();
1✔
156
        }
157

158
        // Test constructor
159
        if (this.checkConstructor != CheckConstructor.OFF) {
1✔
160
            this.constructorsTest();
1✔
161
        }
162

163
        // Test Serializable (internally uses on/off/strict checks)
164
        this.checkSerializableTest();
1✔
165

166
        // Test Equals
167
        if (this.checkEquals == CheckEquals.ON) {
1✔
168
            this.equalsHashCodeToStringSymmetricTest();
1✔
169
        }
170

171
    }
1✔
172

173
    /**
174
     * Getter Setter Tests.
175
     *
176
     * @param instance
177
     *            the instance of class under test.
178
     */
179
    void getterSetterTests(final T instance) {
180
        final PropertyDescriptor[] props = this.getProps(this.clazz);
1✔
181
        for (final PropertyDescriptor prop : props) {
1✔
182
            Method getter = prop.getReadMethod();
1✔
183
            final Method setter = prop.getWriteMethod();
1✔
184

185
            // Java Metro Bug Patch (Boolean Wrapper usage of 'is' possible
186
            if (getter == null && setter != null) {
1!
187
                final String isBooleanWrapper = "is" + setter.getName().substring(3);
1✔
188
                try {
189
                    getter = this.clazz.getMethod(isBooleanWrapper);
1✔
190
                } catch (NoSuchMethodException | SecurityException e) {
1✔
191
                    // Do nothing
192
                }
1✔
193
            }
194

195
            if (getter != null && setter != null) {
1✔
196
                // We have both a get and set method for this property
197
                final Class<?> returnType = getter.getReturnType();
1✔
198
                final Class<?>[] params = setter.getParameterTypes();
1✔
199

200
                if (params.length == 1 && params[0] == returnType) {
1!
201
                    // The set method has 1 argument, which is of the same type as the return type of the get method, so
202
                    // we can test this property
203
                    try {
204
                        // Build a value of the correct type to be passed to the set method
205
                        final Object value = this.buildValue(returnType, LoadType.STANDARD_DATA);
1✔
206

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

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

213
                        // Use data set on instance
214
                        setter.invoke(instance, value);
1✔
215

216
                        final Object expectedValue = value;
1✔
217
                        Object actualValue = getter.invoke(bean);
1✔
218

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

236
                        Assertions.assertEquals(expectedValue, actualValue,
1✔
237
                                String.format("Failed while testing property '%s' of class '%s'", prop.getName(),
1✔
238
                                        this.clazz.getName()));
1✔
239

240
                    } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException
×
241
                            | SecurityException e) {
242
                        Assertions.fail(String.format(
×
243
                                "An exception was thrown while testing class '%s' with the property (getter/setter) '%s': '%s'",
244
                                this.clazz.getName(), prop.getName(), e.toString()));
×
245
                    }
1✔
246
                }
247
            }
248
        }
249
    }
1✔
250

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

289
    /**
290
     * Constructors test.
291
     */
292
    void constructorsTest() {
293
        for (final Constructor<?> constructor : this.clazz.getConstructors()) {
1✔
294

295
            // Skip deprecated constructors
296
            if (constructor.isAnnotationPresent(Deprecated.class)) {
1✔
297
                continue;
1✔
298
            }
299

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

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

304
            // Load Data
305
            for (int i = 0; i < values.length; i++) {
1✔
306
                values[i] = this.buildValue(types[i], LoadType.STANDARD_DATA);
1✔
307
            }
308

309
            try {
310
                constructor.newInstance(values);
1✔
311
            } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
×
312
                Assertions.fail(
×
313
                        String.format("An exception was thrown while testing the constructor(s) '%s' with '%s': '%s'",
×
314
                                constructor.getName(), Arrays.toString(values), e.toString()));
×
315
            }
1✔
316

317
            // TODO 1/12/2019 JWL Add checking of new object properties
318
        }
319
    }
1✔
320

321
    /**
322
     * Check Serializable test.
323
     */
324
    void checkSerializableTest() {
325
        final T object = new ClassInstance<T>().newInstance(this.clazz);
1✔
326
        if (this.implementsSerializable(object)) {
1✔
327
            final T newObject = this.canSerialize(object);
1✔
328
            // Toggle to throw or not throw error with only one way working
329
            if (this.skipStrictSerializable != SkipStrictSerialize.ON) {
1✔
330
                Assertions.assertEquals(object, newObject);
1✔
331
            } else {
332
                Assertions.assertNotEquals(object, newObject);
1✔
333
            }
334
            return;
1✔
335
        }
336

337
        // Only throw error when specifically checking on serialization
338
        if (this.checkSerializable == CheckSerialize.ON) {
1!
339
            Assertions.fail(String.format("Class is not serializable '%s'", object.getClass().getName()));
×
340
        }
341
    }
1✔
342

343
    /**
344
     * Implements serializable.
345
     *
346
     * @param object
347
     *            the object
348
     *
349
     * @return true, if successful
350
     */
351
    boolean implementsSerializable(final T object) {
352
        return object instanceof Externalizable || object instanceof Serializable;
1!
353
    }
354

355
    /**
356
     * Can serialize.
357
     *
358
     * @param object
359
     *            the object
360
     *
361
     * @return object read after serialization
362
     */
363
    @SuppressWarnings("unchecked")
364
    T canSerialize(final T object) {
365
        byte[] byteArray = null;
1✔
366

367
        // Serialize data
368
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
1✔
369
                ObjectOutputStream oos = new ObjectOutputStream(baos)) {
1✔
370
            oos.writeObject(object);
1✔
371
            byteArray = baos.toByteArray();
1✔
372
        } catch (final IOException e) {
×
373
            Assertions.fail(String.format("An exception was thrown while serializing the class '%s': '%s',",
×
374
                    object.getClass().getName(), e.toString()));
×
375
            return null;
×
376
        }
1✔
377

378
        // Deserialize Data
379
        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
1✔
380
                ObjectInputStream ois = new ObjectInputStream(bais)) {
1✔
381
            return (T) ois.readObject();
1✔
382
        } catch (final ClassNotFoundException | IOException e) {
×
383
            Assertions.fail(String.format("An exception was thrown while deserializing the class '%s': '%s',",
×
384
                    object.getClass().getName(), e.toString()));
×
385
        }
386
        return null;
×
387
    }
388

389
    /**
390
     * Builds the value.
391
     *
392
     * @param <R>
393
     *            the generic type
394
     * @param returnType
395
     *            the return type
396
     * @param loadType
397
     *            the load type
398
     *
399
     * @return the object
400
     */
401
    private <R> Object buildValue(final Class<R> returnType, final LoadType loadType) {
402
        final ValueBuilder valueBuilder = new ValueBuilder();
1✔
403
        valueBuilder.setLoadData(this.loadData);
1✔
404
        return valueBuilder.buildValue(returnType, loadType);
1✔
405
    }
406

407
    /**
408
     * Tests the equals/hashCode/toString methods of the specified class.
409
     */
410
    public void equalsHashCodeToStringSymmetricTest() {
411
        // Run Equals Verifier
412
        this.processEqualsVerifierSymmetricTest();
1✔
413

414
        // Create two new instances of the class under test and test equals, hashcode, and toString on them
415
        final T x = new ClassInstance<T>().newInstance(this.clazz);
1✔
416
        final T y = new ClassInstance<T>().newInstance(this.clazz);
1✔
417
        this.processClassEqualsHashCodeToStringSymmetricTest(x, y);
1✔
418

419
        // TODO 1/12/2019 JWL Internalize extension will require canEquals, equals, hashcode, and toString overrides.
420
        /*
421
         * try { this.extension = (Class<E>) new ExtensionBuilder<T>().generate(this.clazz); } catch (NotFoundException
422
         * e) { Assert.fail(e.getMessage()); } catch (CannotCompileException e) { Assert.fail(e.getMessage()); }
423
         */
424

425
        // If there is an extension, create an instance and test equals, hashcode, and toString on it against the empty
426
        // class instance
427
        if (this.extension != null) {
1✔
428
            final E ext = new ClassInstance<E>().newInstance(this.extension);
1✔
429
            this.processExtensionEqualsHashCodeToStringSymmetricTest(y, ext);
1✔
430
        }
431
    }
1✔
432

433
    /**
434
     * Process equals verifier symmetric test.
435
     */
436
    private void processEqualsVerifierSymmetricTest() {
437
        // Run Equals Verifier
438
        try {
439
            EqualsVerifier.simple().forClass(this.clazz).suppress(Warning.BIGDECIMAL_EQUALITY).verify();
1✔
440
        } catch (AssertionError e) {
×
441
            JavaBeanTesterWorker.logger.warn("EqualsVerifier attempt failed: {}", e.getMessage());
×
442
        }
1✔
443
    }
1✔
444

445
    /**
446
     * Process class equals hash code to string symmetric test.
447
     *
448
     * @param x
449
     *            the x
450
     * @param y
451
     *            the y
452
     */
453
    private void processClassEqualsHashCodeToStringSymmetricTest(final T x, final T y) {
454
        // Verify that the objects were created successfully before testing equals, hashcode, and toString
455
        Assertions.assertNotNull(x,
1✔
456
                String.format("Create new instance of class '%s' resulted in null", this.clazz.getName()));
1✔
457
        Assertions.assertNotNull(y,
1✔
458
                String.format("Create new instance of class '%s' resulted in null", this.clazz.getName()));
1✔
459

460
        // Test Equals, HashCode, and ToString on Empty Objects
461
        Assertions.assertEquals(x, y,
1✔
462
                String.format(".equals() should be consistent for two empty objects of type %s", this.clazz.getName()));
1✔
463
        Assertions.assertEquals(x.hashCode(), y.hashCode(), String
1✔
464
                .format(".hashCode() should be consistent for two empty objects of type %s", this.clazz.getName()));
1✔
465
        Assertions.assertEquals(x.toString(), y.toString(), String
1✔
466
                .format(".toString() should be consistent for two empty objects of type %s", this.clazz.getName()));
1✔
467

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

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

480
            // ReTest Equals (flip)
481
            Assertions.assertNotEquals(y, x,
1✔
482
                    String.format(".equals() should not be consistent for one empty and one loaded object of type %s",
1✔
483
                            this.clazz.getName()));
1✔
484

485
            // Populate Side Y
486
            JavaBeanTesterWorker.load(this.clazz, y, this.loadData);
1✔
487

488
            // ReTest Equals and HashCode
489
            if (this.loadData == LoadData.ON) {
1✔
490
                Assertions.assertEquals(x, y,
1✔
491
                        String.format(".equals() should be equal for two instances of type %s with loaded data",
1✔
492
                                this.clazz.getName()));
1✔
493
                Assertions.assertEquals(x.hashCode(), y.hashCode(),
1✔
494
                        String.format(".hashCode() should be equal for two instances of type %s with loaded data",
1✔
495
                                this.clazz.getName()));
1✔
496
            } else {
497
                // Part of the data loaded may be the same, so we cannot guarantee that equals and hashcode will be
498
                // different, but we can at least check that they are not both the same
499
                // Assertions.assertNotEquals(x, y);
500
                // Assertions.assertNotEquals(x.hashCode(), y.hashCode());
501
                if (x.hashCode() == y.hashCode()) {
1✔
502
                    Assertions.assertEquals(x, y);
1✔
503
                    Assertions.assertEquals(x.hashCode(), y.hashCode());
1✔
504
                } else {
505
                    Assertions.assertNotEquals(x, y);
1✔
506
                    Assertions.assertNotEquals(x.hashCode(), y.hashCode());
1✔
507
                }
508
            }
509
        }
510

511
        // Create Immutable Instance
512
        try {
513
            final T e = new ClassInstance<T>().newInstance(this.clazz);
1✔
514
            ByteBuddyBeanCopier.copy(x, e, (value, targetType) -> {
1✔
515
                if (targetType == boolean.class) {
1✔
516
                    return value == null ? Boolean.FALSE : value;
1!
517
                }
518
                return value;
1✔
519
            });
520
            Assertions.assertEquals(e, x);
1✔
521
        } catch (final Exception e) {
×
522
            JavaBeanTesterWorker.logger.trace("Do nothing class is not mutable", e);
×
523
        }
1✔
524
    }
1✔
525

526
    /**
527
     * Process extension equals hash code to string symmetric test.
528
     *
529
     * @param y
530
     *            the y
531
     * @param ext
532
     *            the ext
533
     */
534
    private void processExtensionEqualsHashCodeToStringSymmetricTest(final T y, final E ext) {
535
        // Verify that the objects were created successfully before testing equals, hashcode, and toString
536
        Assertions.assertNotNull(ext,
1✔
537
                String.format("Create new instance of extension %s resulted in null", this.extension.getName()));
1✔
538

539
        // Test Extension Equals, HashCode, and ToString on Empty Objects
540
        Assertions.assertNotEquals(ext, y,
1✔
541
                String.format(".equals() should not be equal for extension of type %s and empty object of type %s",
1✔
542
                        this.extension.getName(), this.clazz.getName()));
1✔
543
        Assertions.assertNotEquals(ext.hashCode(), y.hashCode(),
1✔
544
                String.format(".hashCode() should not be equal for extension of type %s and empty object of type %s",
1✔
545
                        this.extension.getName(), this.clazz.getName()));
1✔
546
        Assertions.assertNotEquals(ext.toString(), y.toString(),
1✔
547
                String.format(".toString() should not be equal for extension of type %s and empty object of type %s",
1✔
548
                        this.extension.getName(), this.clazz.getName()));
1✔
549

550
        // Test Extension One Sided Tests on Empty Objects
551
        Assertions.assertNotEquals(ext, null,
1✔
552
                String.format("An empty extension of type %s should not be equal to null", this.clazz.getName()));
1✔
553
        Assertions.assertEquals(ext, ext,
1✔
554
                String.format("An empty extension of type %s should be equal to itself", this.extension.getName()));
1✔
555

556
        // If the class has setters, the previous tests would have been against empty classes
557
        // If so, load the classes and re-test
558
        if (this.classHasSetters(this.clazz)) {
1✔
559
            // Populate Extension Side Ext
560
            JavaBeanTesterWorker.load(this.extension, ext, this.loadData);
1✔
561

562
            // ReTest Extension Equals (flip)
563
            Assertions.assertNotEquals(y, ext,
1✔
564
                    String.format(".equals() should not be equal for extension of type %s and empty object of type %s",
1✔
565
                            this.extension.getName(), this.clazz.getName()));
1✔
566

567
            // Populate Side Y
568
            JavaBeanTesterWorker.load(this.clazz, y, this.loadData);
1✔
569

570
            // ReTest Extension Equals, HashCode, and ToString
571
            Assertions.assertNotEquals(ext, y,
1✔
572
                    String.format(".equals() should not be equal for extension of type %s and empty object of type %s",
1✔
573
                            this.extension.getName(), this.clazz.getName()));
1✔
574
            Assertions.assertNotEquals(ext.hashCode(), y.hashCode(),
1✔
575
                    String.format(
1✔
576
                            ".hashCode() should not be equal for extension of type %s and empty object of type %s",
577
                            this.extension.getName(), this.clazz.getName()));
1✔
578
            Assertions.assertNotEquals(ext.toString(), y.toString(),
1✔
579
                    String.format(
1✔
580
                            ".toString() should not be equal for extension of type %s and empty object of type %s",
581
                            this.extension.getName(), this.clazz.getName()));
1✔
582
        }
583

584
        // If class is final, use Object.class for comparison needs
585
        if (Modifier.isFinal(clazz.getModifiers())) {
1✔
586
            JavaBeanTesterWorker.logger.trace("Final object does not go through final equals check");
1✔
587
            return;
1✔
588
        }
589

590
        // Create Extension Immutable Instance
591
        try {
592
            final E e = new ClassInstance<E>().newInstance(this.extension);
1✔
593
            ByteBuddyBeanCopier.copy(ext, e, (value, targetType) -> {
1✔
594
                if (targetType == boolean.class) {
1✔
595
                    return value == null ? Boolean.FALSE : value;
1!
596
                }
597
                return value;
1✔
598
            });
599
            Assertions.assertEquals(e, ext);
1✔
600
        } catch (final Exception e) {
×
601
            JavaBeanTesterWorker.logger.trace("Do nothing class is not mutable", e);
×
602
        }
1✔
603
    }
1✔
604

605
    /**
606
     * Equals Tests will traverse one object changing values until all have been tested against another object. This is
607
     * done to effectively test all paths through equals.
608
     *
609
     * @param instance
610
     *            the class instance under test.
611
     * @param expected
612
     *            the instance expected for tests.
613
     */
614
    void equalsTests(final T instance, final T expected) {
615

616
        // Perform hashCode test dependent on data coming in
617
        // Assert.assertEquals(expected.hashCode(), instance.hashCode());
618
        if (expected.hashCode() == instance.hashCode()) {
1✔
619
            Assertions.assertEquals(expected.hashCode(), instance.hashCode());
1✔
620
        } else {
621
            Assertions.assertNotEquals(expected.hashCode(), instance.hashCode());
1✔
622
        }
623

624
        final ValueBuilder valueBuilder = new ValueBuilder();
1✔
625
        valueBuilder.setLoadData(this.loadData);
1✔
626

627
        final PropertyDescriptor[] props = this.getProps(instance.getClass());
1✔
628
        for (final PropertyDescriptor prop : props) {
1✔
629
            Method getter = prop.getReadMethod();
1✔
630
            final Method setter = prop.getWriteMethod();
1✔
631

632
            // Java Metro Bug Patch (Boolean Wrapper usage of 'is' possible
633
            if (getter == null && setter != null) {
1!
634
                final String isBooleanWrapper = "is" + setter.getName().substring(3);
1✔
635
                try {
636
                    getter = this.clazz.getMethod(isBooleanWrapper);
1✔
637
                } catch (NoSuchMethodException | SecurityException e) {
×
638
                    // Do nothing
639
                }
1✔
640
            }
641

642
            if (getter != null && setter != null) {
1!
643
                // We have both a get and set method for this property
644
                final Class<?> returnType = getter.getReturnType();
1✔
645
                final Class<?>[] params = setter.getParameterTypes();
1✔
646

647
                if (params.length == 1 && params[0] == returnType) {
1!
648
                    // The set method has 1 argument, which is of the same type as the return type of the get method, so
649
                    // we can test this property
650
                    try {
651
                        // Save original value
652
                        final Object original = getter.invoke(instance);
1✔
653

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

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

660
                        // Check equals depending on data
661
                        if (instance.equals(expected)) {
1✔
662
                            Assertions.assertEquals(expected, instance);
1✔
663
                        } else {
664
                            Assertions.assertNotEquals(expected, instance);
1✔
665
                        }
666

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

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

673
                        // Check equals depending on data
674
                        if (instance.equals(expected)) {
1✔
675
                            Assertions.assertEquals(expected, instance);
1✔
676
                        } else {
677
                            Assertions.assertNotEquals(expected, instance);
1✔
678
                        }
679

680
                        // Reset to original value
681
                        setter.invoke(instance, original);
1✔
682

683
                    } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException
×
684
                            | SecurityException e) {
685
                        Assertions.fail(
×
686
                                String.format("An exception was thrown while testing the property (equals) '%s': '%s'",
×
687
                                        prop.getName(), e.toString()));
×
688
                    }
1✔
689
                }
690
            }
691
        }
692
    }
1✔
693

694
    /**
695
     * Class has setters.
696
     *
697
     * @param clazz
698
     *            the clazz
699
     *
700
     * @return true, if successful
701
     */
702
    private boolean classHasSetters(final Class<T> clazz) {
703
        return Arrays.stream(this.getProps(clazz))
1✔
704
                .anyMatch(propertyDescriptor -> propertyDescriptor.getWriteMethod() != null);
1✔
705
    }
706

707
    /**
708
     * Gets the props.
709
     *
710
     * @param clazz
711
     *            the clazz
712
     *
713
     * @return the props
714
     */
715
    private PropertyDescriptor[] getProps(final Class<?> clazz) {
716
        try {
717
            final List<PropertyDescriptor> usedProps = new ArrayList<>(
1✔
718
                    Introspector.getBeanInfo(clazz).getPropertyDescriptors().length);
1✔
719
            final List<PropertyDescriptor> props = Arrays
1✔
720
                    .asList(Introspector.getBeanInfo(clazz).getPropertyDescriptors());
1✔
721
            for (final PropertyDescriptor prop : props) {
1✔
722
                // Check the list of properties that we don't want to test
723
                if (this.skipThese.contains(prop.getName())) {
1✔
724
                    continue;
1✔
725
                }
726
                usedProps.add(prop);
1✔
727
            }
1✔
728
            return usedProps.toArray(new PropertyDescriptor[0]);
1✔
729
        } catch (final IntrospectionException e) {
×
730
            Assertions.fail(String.format("An exception was thrown while testing class '%s': '%s'",
×
731
                    this.clazz.getName(), e.toString()));
×
732
            return new PropertyDescriptor[0];
×
733
        }
734
    }
735

736
}
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