|
| 1 | +package pl.mperor.lab.java.idiom; |
| 2 | + |
| 3 | +import org.junit.jupiter.api.Assertions; |
| 4 | +import org.junit.jupiter.api.Test; |
| 5 | + |
| 6 | +import java.util.ArrayList; |
| 7 | +import java.util.List; |
| 8 | +import java.util.Objects; |
| 9 | + |
| 10 | +public class RecordsInDepthTest { |
| 11 | + |
| 12 | + @Test |
| 13 | + public void testCompactConstructorValidatesAndNormalizesName() { |
| 14 | + var team = new Team(" Falcons ", List.of("Ann", "Bob")); |
| 15 | + Assertions.assertEquals("Falcons", team.name(), "the compact constructor strips surrounding whitespace"); |
| 16 | + |
| 17 | + Assertions.assertThrows(NullPointerException.class, () -> new Team(null, List.of("Ann"))); |
| 18 | + Assertions.assertThrows(IllegalArgumentException.class, () -> new Team(" ", List.of("Ann"))); |
| 19 | + } |
| 20 | + |
| 21 | + @Test |
| 22 | + public void testCompactConstructorDefensiveCopyOfMembers() { |
| 23 | + var mutableMembers = new ArrayList<>(List.of("Ann", "Bob")); |
| 24 | + var team = new Team("Falcons", mutableMembers); |
| 25 | + |
| 26 | + mutableMembers.add("Cid"); |
| 27 | + Assertions.assertEquals(2, team.members().size(), "the record keeps its own copy, unaffected by later mutation of the source list"); |
| 28 | + Assertions.assertThrows(UnsupportedOperationException.class, () -> team.members().add("Dee"), "List.copyOf() returns an immutable list"); |
| 29 | + } |
| 30 | + |
| 31 | + @Test |
| 32 | + public void testWitherReturnsModifiedCopyAndLeavesOriginalUnchanged() { |
| 33 | + var falcons = new Team("Falcons", List.of("Ann", "Bob")); |
| 34 | + var eagles = falcons.withName("Eagles"); |
| 35 | + |
| 36 | + Assertions.assertEquals("Eagles", eagles.name()); |
| 37 | + Assertions.assertEquals("Falcons", falcons.name(), "withName() returns a new instance; the original record is unchanged"); |
| 38 | + Assertions.assertNotEquals(falcons, eagles); |
| 39 | + Assertions.assertSame(falcons.members(), eagles.members(), "the untouched component is shared, not re-copied"); |
| 40 | + } |
| 41 | + |
| 42 | + record Team(String name, List<String> members) { |
| 43 | + Team { |
| 44 | + Objects.requireNonNull(name, "name must not be null"); |
| 45 | + if (name.isBlank()) { |
| 46 | + throw new IllegalArgumentException("name must not be blank"); |
| 47 | + } |
| 48 | + name = name.strip(); |
| 49 | + members = List.copyOf(members); |
| 50 | + } |
| 51 | + |
| 52 | + Team withName(String newName) { |
| 53 | + return new Team(newName, members); |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + @Test |
| 58 | + public void testArrayComponentBreaksGeneratedEqualsAndHashCode() { |
| 59 | + var badge1 = new Badge("A1", new byte[] {1, 2, 3}); |
| 60 | + var badge2 = new Badge("A1", new byte[] {1, 2, 3}); |
| 61 | + |
| 62 | + // Generated equals()/hashCode() compare array components by reference, |
| 63 | + // not content, so two "equal looking" badges are reported as different. |
| 64 | + Assertions.assertNotEquals(badge1, badge2); |
| 65 | + Assertions.assertArrayEquals(badge1.data(), badge2.data(), "the array contents are in fact identical"); |
| 66 | + } |
| 67 | + |
| 68 | + record Badge(String id, byte[] data) {} |
| 69 | +} |
0 commit comments