Skip to content

Commit ea35b4f

Browse files
claudemperor
authored andcommitted
Add records-in-depth idiom test: validation, defensive copy, withers, array pitfall
1 parent 2e6e257 commit ea35b4f

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

‎FirstSteps/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
### Idioms 💡
4343

4444
- [Data-Oriented Programming](src/test/java/pl/mperor/lab/java/idiom/DataOrientedProgrammingTest.java)
45+
- [Records in Depth](src/test/java/pl/mperor/lab/java/idiom/RecordsInDepthTest.java)
4546

4647
### Quirks 🤔
4748

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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

Comments
 (0)