1
2
3
4
5
6
7
8
9 package com.codebox.bean;
10
11 import java.lang.reflect.Constructor;
12 import java.lang.reflect.InvocationTargetException;
13 import java.lang.reflect.Method;
14
15 import lombok.Data;
16
17 import org.junit.jupiter.api.Assertions;
18 import org.junit.jupiter.api.Test;
19
20
21
22
23 class ByteBuddyBeanCopierTest {
24
25
26
27
28 @Data
29 static class SourceBean {
30
31 private String name;
32
33
34 private int age;
35
36
37 private boolean active;
38
39
40 private Boolean enabled;
41 }
42
43
44
45
46 @Data
47 static class TargetBean {
48
49 private String name;
50
51
52 private int age;
53
54
55 private boolean active;
56
57
58 private Boolean enabled;
59 }
60
61
62
63
64 @Test
65 void testCopyNullSourceOrTarget() {
66 var target = new TargetBean();
67 Assertions.assertDoesNotThrow(() -> ByteBuddyBeanCopier.copy(null, target, null));
68 Assertions.assertDoesNotThrow(() -> ByteBuddyBeanCopier.copy(new SourceBean(), null, null));
69 }
70
71 @Test
72 void testCopyPropertiesAndConverter() {
73 var source = new SourceBean();
74 source.setName("name");
75 source.setAge(42);
76 source.setActive(true);
77 source.setEnabled(Boolean.TRUE);
78
79 var target = new SourceBean();
80
81 ByteBuddyBeanCopier.copy(source, target, (value, targetType) -> Boolean.FALSE);
82
83 Assertions.assertEquals("name", target.getName());
84 Assertions.assertEquals(42, target.getAge());
85 Assertions.assertFalse(target.isActive());
86 Assertions.assertFalse(target.getEnabled());
87 }
88
89 @Test
90 void testCopySkipsPrimitiveBooleanWhenNoGetter() {
91 var source = new PrimitiveSetterOnlySource();
92 source.setActive(false);
93
94 var target = new PrimitiveSetterOnlySource();
95 target.setActive(true);
96
97 ByteBuddyBeanCopier.copy(source, target, null);
98
99 Assertions.assertTrue(target.active());
100 }
101
102 @Test
103 void testCopySkipsPrimitiveBooleanWhenFallbackIsMethodIsNotBoolean() {
104 var source = new NonBooleanIsMethodSource();
105 source.setActive(false);
106
107 var target = new NonBooleanIsMethodSource();
108 target.setActive(true);
109
110 ByteBuddyBeanCopier.copy(source, target, null);
111
112 Assertions.assertTrue(target.active());
113 }
114
115 @Test
116 void testCopyWrapsGetterInvocationFailures() {
117 var source = new FailingGetterSource();
118 var target = new FailingGetterSource();
119
120 RuntimeException exception = Assertions.assertThrows(RuntimeException.class,
121 () -> ByteBuddyBeanCopier.copy(source, target, null));
122 Assertions.assertTrue(exception.getMessage().startsWith("Failed to copy property"));
123 }
124
125 @Test
126 void testCopyUsesBooleanIsFallbackGetter() {
127 var source = new BooleanIsFallbackSource();
128 source.setActive(Boolean.TRUE);
129
130 var target = new BooleanIsFallbackSource();
131 ByteBuddyBeanCopier.copy(source, target, null);
132
133 Assertions.assertTrue(target.isActive());
134 }
135
136 @Test
137 void testUtilityConstructorThrowsAssertionError() throws Exception {
138 Constructor<ByteBuddyBeanCopier> constructor = ByteBuddyBeanCopier.class.getDeclaredConstructor();
139 constructor.setAccessible(true);
140
141 InvocationTargetException exception = Assertions.assertThrows(InvocationTargetException.class,
142 constructor::newInstance);
143 Assertions.assertInstanceOf(AssertionError.class, exception.getCause());
144 }
145
146 @Test
147 void testExtractPropertyNameRejectsNonGetterMethod() throws Exception {
148 Method extract = ByteBuddyBeanCopier.class.getDeclaredMethod("extractPropertyName", Method.class);
149 extract.setAccessible(true);
150
151 Method nonGetter = NonGetterMethodBean.class.getMethod("name");
152 InvocationTargetException exception = Assertions.assertThrows(InvocationTargetException.class,
153 () -> extract.invoke(null, nonGetter));
154 Assertions.assertInstanceOf(IllegalArgumentException.class, exception.getCause());
155 }
156
157 @Test
158 void testCopyUsesBooleanFallbackWithMismatchedSetterPropertyName() {
159 var source = new BooleanFallbackMismatchBean();
160 source.setuRL(true);
161
162 var target = new BooleanFallbackMismatchBean();
163 ByteBuddyBeanCopier.copy(source, target, null);
164
165 Assertions.assertTrue(target.isURL());
166 }
167
168 static class PrimitiveSetterOnlySource {
169 private boolean active;
170
171 public void setActive(boolean active) {
172 this.active = active;
173 }
174
175 public boolean active() {
176 return this.active;
177 }
178 }
179
180 static class NonBooleanIsMethodSource {
181 private boolean active;
182
183 public void setActive(boolean active) {
184 this.active = active;
185 }
186
187 public boolean active() {
188 return this.active;
189 }
190
191 public String isActive() {
192 return "not-boolean";
193 }
194 }
195
196 static class BooleanIsFallbackSource {
197 private Boolean active;
198
199 public void setActive(Boolean active) {
200 this.active = active;
201 }
202
203 public boolean isActive() {
204 return Boolean.TRUE.equals(this.active);
205 }
206 }
207
208 static class NonGetterMethodBean {
209 public String name() {
210 return "value";
211 }
212 }
213
214 static class BooleanFallbackMismatchBean {
215 private boolean url;
216
217 @SuppressWarnings("java:S100")
218 public void setuRL(final boolean url) {
219 this.url = url;
220 }
221
222 public boolean isURL() {
223 return this.url;
224 }
225 }
226
227
228
229
230 @Test
231 void testCopyWithAcronymGetter() {
232 var source = new AcronymBean();
233 source.setURL("https://example.com");
234
235 var target = new AcronymBean();
236 ByteBuddyBeanCopier.copy(source, target, null);
237
238 Assertions.assertEquals("https://example.com", target.getURL());
239 }
240
241
242
243
244
245 @Test
246 void testCopyWithEmptyPropertyName() {
247 var source = new EmptyPropertyNameBean();
248 var target = new EmptyPropertyNameBean();
249 Assertions.assertDoesNotThrow(() -> ByteBuddyBeanCopier.copy(source, target, null));
250 }
251
252
253
254
255
256 @Test
257 void testCopyWithVoidReturningGetterMethod() {
258 var source = new VoidGetterBean();
259 var target = new VoidGetterBean();
260
261 Assertions.assertDoesNotThrow(() -> ByteBuddyBeanCopier.copy(source, target, null));
262 }
263
264
265
266
267
268
269 @Test
270 void testCopyWithSingleCharAndExactGetProperty() {
271 var source = new SingleCharPropertyBean();
272 source.setX("hello");
273
274 var target = new SingleCharPropertyBean();
275 ByteBuddyBeanCopier.copy(source, target, null);
276
277 Assertions.assertEquals("hello", target.getX());
278 }
279
280
281
282
283
284 @Test
285 void testCopyWithFluentSetter() {
286 var source = new FluentSetterBean();
287 source.setName("test");
288
289 var target = new FluentSetterBean();
290 ByteBuddyBeanCopier.copy(source, target, null);
291
292
293 Assertions.assertNull(target.getName());
294 }
295
296 static class AcronymBean {
297
298 private String url;
299
300
301
302
303
304
305 public String getURL() {
306 return this.url;
307 }
308
309
310
311
312
313
314
315 public void setURL(final String url) {
316 this.url = url;
317 }
318 }
319
320
321 static class EmptyPropertyNameBean {
322
323 @SuppressWarnings({ "unused", "java:S100" })
324 private boolean val;
325
326
327
328
329
330
331
332 @SuppressWarnings("java:S100")
333 public void set(final boolean val) {
334 this.val = val;
335 }
336 }
337
338
339 static class VoidGetterBean {
340
341 private String name;
342
343
344
345
346 @SuppressWarnings("java:S4144")
347 public void getName() {
348
349 }
350
351
352
353
354
355
356
357 public void setName(final String name) {
358 this.name = name;
359 }
360 }
361
362
363
364
365
366 static class SingleCharPropertyBean {
367
368 private String x;
369
370
371
372
373
374
375 public String getX() {
376 return this.x;
377 }
378
379
380
381
382
383
384
385 public void setX(final String x) {
386 this.x = x;
387 }
388
389
390
391
392
393
394 @SuppressWarnings("java:S100")
395 public String get() {
396 return null;
397 }
398 }
399
400
401 static class FluentSetterBean {
402
403 private String name;
404
405
406
407
408
409
410 public String getName() {
411 return this.name;
412 }
413
414
415
416
417
418
419
420
421 public FluentSetterBean setName(final String name) {
422 this.name = name;
423 return this;
424 }
425 }
426
427 static class FailingGetterSource {
428 public void setName(String name) {
429 }
430
431 public String getName() {
432 throw new IllegalStateException("boom");
433 }
434 }
435
436 }