QA Interview Hub · Knowledge base · Quizzes

Java basics

Collections and test data

List, Set, Map and generics in test data

The main collections (package java.util):

  • List — an ordered list with duplicates and access by index. ArrayList is the default choice. The result of findElements, table rows, items of an API response are usually a List.
  • Set — unique elements. HashSet has no order, LinkedHashSet keeps insertion order, TreeSet is sorted. Handy to check for duplicates: new HashSet<>(list).size() == list.size().
  • Map — key-value pairs. HashMap has no order, LinkedHashMap keeps insertion order, TreeMap orders by key. Convenient for test data by role or environment: Map<Role, User>.

Immutable collections: List.of(...), Set.of(...), Map.of(...) allow no changes and no null; add throws UnsupportedOperationException. Arrays.asList(...) has a fixed size: you can replace an element but not add one. For a mutable copy use new ArrayList<>(List.of(...)).

Generics (List<String>) move type checking to compile time: no casts, a type error is visible at once. Do not use raw types (List without a parameter). At run time the type parameter is erased (type erasure), so new T() and instanceof List<String> are impossible; this matters when Jackson deserialises a List<User> and needs a TypeReference.

Do not modify a collection while iterating: removing an element inside a for-each loop throws ConcurrentModificationException. Use removeIf or an iterator.

hashCode/equals, reading JSON and .properties, Jackson and POJOs

The equals/hashCode contract. If a.equals(b), then a.hashCode() == b.hashCode(). HashMap and HashSet first find a bucket by the hash and then compare with equals. If you override only equals, equal objects get different hashes and set.contains(...) will not find the element. Always override both together (or use a record, Lombok @EqualsAndHashCode). Do not put into map keys objects that change fields used in the hash.

Test data files. They live in src/test/resources and end up on the classpath. Read them through the classpath, not through a relative path that depends on the working directory:

try (InputStream in = getClass().getResourceAsStream("/users.json")) {
    List<User> users = mapper.readValue(in, new TypeReference<List<User>>() {});
}

.properties: Properties props = new Properties(); props.load(in);, a value is props.getProperty("baseUrl", "http://localhost"). Do not keep secrets in a file in the repository: take them from environment variables.

Jackson (ObjectMapper) turns JSON into a POJO and back. A POJO is a plain class (or record) with fields and accessors and no dependency on a framework.

  • It needs a no-argument constructor with setters, or a record, or @JsonCreator.
  • In Jackson 2.x an unknown field in JSON gives UnrecognizedPropertyException by default (FAIL_ON_UNKNOWN_PROPERTIES). For API responses that may grow, add @JsonIgnoreProperties(ignoreUnknown = true) or turn this option off in the mapper. Jackson 3 turns this option off by default.
  • @JsonProperty("user_id") maps a field to a different name; LocalDate/Instant need the module jackson-datatype-jsr310 in Jackson 2.x (Jackson 3 has java.time support built in), and @JsonFormat sets the format.
  • ObjectMapper is thread-safe after configuration: create one and reuse it, because creation is costly.