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

hazendaz / javabean-tester / 2725

29 Jun 2025 07:35PM UTC coverage: 89.922%. Remained the same
2725

push

github

hazendaz
Fix warnings from equals verifier as we don't necessary care about strict bigdecimal checks on equality

210 of 231 branches covered (90.91%)

1 of 1 new or added line in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

464 of 516 relevant lines covered (89.92%)

0.9 hits per line

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

86.45
/src/main/java/com/codebox/bean/JavaBeanTesterWorker.java
1
/*
2
 * JavaBean Tester (https://github.com/hazendaz/javabean-tester)
3
 *
4
 * Copyright 2012-2023 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.util.ArrayList;
41
import java.util.Arrays;
42
import java.util.Date;
43
import java.util.HashSet;
44
import java.util.List;
45
import java.util.Set;
46

47
import lombok.Data;
48

49
import net.sf.cglib.beans.BeanCopier;
50

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

148
        return worker;
1✔
149
    }
150

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

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

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

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

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

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

178
    }
1✔
179

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

364
    /**
365
     * Can serialize.
366
     *
367
     * @param object
368
     *            the object
369
     *
370
     * @return object read after serialization
371
     */
372
    @SuppressWarnings("unchecked")
373
    T canSerialize(final T object) {
374
        // Serialize data
375
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1✔
376
        try {
377
            new ObjectOutputStream(baos).writeObject(object);
1✔
378
        } catch (final IOException e) {
×
379
            Assertions.fail(String.format("An exception was thrown while serializing the class '%s': '%s',",
×
380
                    object.getClass().getName(), e.toString()));
×
381
            return null;
×
382
        }
1✔
383

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

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

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

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

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

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

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

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

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

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

468
        // Test Extension One Sided Tests on Empty Objects
469
        Assertions.assertNotEquals(ext, null,
1✔
470
                String.format("An empty extension of type %s should not be equal to null", this.clazz.getName()));
1✔
471
        Assertions.assertEquals(ext, ext,
1✔
472
                String.format("An empty extension of type %s should be equal to itself", this.extension.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
            // Populate Extension Side Ext
481
            JavaBeanTesterWorker.load(this.extension, ext, this.loadData);
1✔
482

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

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

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

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

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

523
        // Create Immutable Instance
524
        try {
525
            final BeanCopier clazzBeanCopier = BeanCopier.create(this.clazz, this.clazz, true);
1✔
526
            final T e = new ClassInstance<T>().newInstance(this.clazz);
1✔
527
            clazzBeanCopier.copy(x, e, null);
1✔
528
            Assertions.assertEquals(e, x);
1✔
529
        } catch (final Exception e) {
1✔
530
            JavaBeanTesterWorker.LOGGER.trace("Do nothing class is not mutable", e);
1✔
531
        }
1✔
532

533
        // Create Extension Immutable Instance
534
        try {
535
            final BeanCopier extensionBeanCopier = BeanCopier.create(this.extension, this.extension, true);
1✔
536
            final E e = new ClassInstance<E>().newInstance(this.extension);
1✔
537
            extensionBeanCopier.copy(ext, e, null);
×
538
            Assertions.assertEquals(e, ext);
×
539
        } catch (final Exception e) {
1✔
540
            JavaBeanTesterWorker.LOGGER.trace("Do nothing class is not mutable", e);
1✔
541
        }
×
542
    }
1✔
543

544
    /**
545
     * Equals Tests will traverse one object changing values until all have been tested against another object. This is
546
     * done to effectively test all paths through equals.
547
     *
548
     * @param instance
549
     *            the class instance under test.
550
     * @param expected
551
     *            the instance expected for tests.
552
     */
553
    void equalsTests(final T instance, final T expected) {
554

555
        // Perform hashCode test dependent on data coming in
556
        // Assert.assertEquals(expected.hashCode(), instance.hashCode());
557
        if (expected.hashCode() == instance.hashCode()) {
1✔
558
            Assertions.assertEquals(expected.hashCode(), instance.hashCode());
1✔
559
        } else {
560
            Assertions.assertNotEquals(expected.hashCode(), instance.hashCode());
1✔
561
        }
562

563
        final ValueBuilder valueBuilder = new ValueBuilder();
1✔
564
        valueBuilder.setLoadData(this.loadData);
1✔
565

566
        final PropertyDescriptor[] props = this.getProps(instance.getClass());
1✔
567
        for (final PropertyDescriptor prop : props) {
1✔
568
            Method getter = prop.getReadMethod();
1✔
569
            final Method setter = prop.getWriteMethod();
1✔
570

571
            // Java Metro Bug Patch (Boolean Wrapper usage of 'is' possible
572
            if (getter == null && setter != null) {
1!
573
                final String isBooleanWrapper = "is" + setter.getName().substring(3);
1✔
574
                try {
575
                    getter = this.clazz.getMethod(isBooleanWrapper);
1✔
576
                } catch (NoSuchMethodException | SecurityException e) {
×
577
                    // Do nothing
578
                }
1✔
579
            }
580

581
            if (getter != null && setter != null) {
1!
582
                // We have both a get and set method for this property
583
                final Class<?> returnType = getter.getReturnType();
1✔
584
                final Class<?>[] params = setter.getParameterTypes();
1✔
585

586
                if (params.length == 1 && params[0] == returnType) {
1!
587
                    // The set method has 1 argument, which is of the same type as the return type of the get method, so
588
                    // we can test this property
589
                    try {
590
                        // Save original value
591
                        final Object original = getter.invoke(instance);
1✔
592

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

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

599
                        // Check equals depending on data
600
                        if (instance.equals(expected)) {
1✔
601
                            Assertions.assertEquals(expected, instance);
1✔
602
                        } else {
603
                            Assertions.assertNotEquals(expected, instance);
1✔
604
                        }
605

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

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

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

619
                        // Reset to original value
620
                        setter.invoke(instance, original);
1✔
621

622
                    } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException
×
623
                            | SecurityException e) {
624
                        Assertions.fail(
×
625
                                String.format("An exception was thrown while testing the property (equals) '%s': '%s'",
×
626
                                        prop.getName(), e.toString()));
×
627
                    }
1✔
628
                }
629
            }
630
        }
631
    }
1✔
632

633
    /**
634
     * Class has setters.
635
     *
636
     * @param clazz
637
     *            the clazz
638
     *
639
     * @return true, if successful
640
     */
641
    private boolean classHasSetters(final Class<T> clazz) {
642
        return Arrays.stream(this.getProps(clazz))
1✔
643
                .anyMatch(propertyDescriptor -> propertyDescriptor.getWriteMethod() != null);
1✔
644
    }
645

646
    /**
647
     * Gets the props.
648
     *
649
     * @param clazz
650
     *            the clazz
651
     *
652
     * @return the props
653
     */
654
    private PropertyDescriptor[] getProps(final Class<?> clazz) {
655
        try {
656
            final List<PropertyDescriptor> usedProps = new ArrayList<>(
1✔
657
                    Introspector.getBeanInfo(clazz).getPropertyDescriptors().length);
1✔
658
            final List<PropertyDescriptor> props = Arrays
1✔
659
                    .asList(Introspector.getBeanInfo(clazz).getPropertyDescriptors());
1✔
660
            nextProp: for (final PropertyDescriptor prop : props) {
1✔
661
                // Check the list of properties that we don't want to test
662
                for (final String skipThis : this.skipThese) {
1✔
663
                    if (skipThis.equals(prop.getName())) {
1✔
664
                        continue nextProp;
1✔
665
                    }
666
                }
1✔
667
                usedProps.add(prop);
1✔
668
            }
1✔
669
            return usedProps.toArray(new PropertyDescriptor[usedProps.size()]);
1✔
670
        } catch (final IntrospectionException e) {
×
671
            Assertions.fail(String.format("An exception was thrown while testing class '%s': '%s'",
×
672
                    this.clazz.getName(), e.toString()));
×
673
            return new PropertyDescriptor[0];
×
674
        }
675
    }
676

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

© 2026 Coveralls, Inc