ChatGPT says, “When you create an instance of a child class in Java, by default, an instance of the parent class is also created. This happens because the child class constructor implicitly calls the parent class constructor using the super() statement as the first line of the child class constructor. […] If you have a five-tier class hierarchy where each class extends the previous one, calling a constructor of the most “junior” class will result in creating an instance of each of the five classes.”
However, I’m not sure it’s true. See, I have a parent that has an autowired field. I try to use it in a child constructor to initialize that child’s field and get an NullPointerException
: the parent field, in the code below it’s testUtil
, is not yet initialized. It means field injections of the parent didn’t happen which, in turn, seems to mean that an instance of the parent has not been created (despite the execution of the super()
constructor). After all, Spring performs field injections right after object creation (unless I’m mistaken)
So how does it work?
public class UserQuestionCommentRestControllerTest extends IntegrationTestContext {
private static final String testUsername = "mickey_m";
private static final String testPassword = "mickey";
private final String token;
private static final String BASE_URI = "/api/v1/user/question-comments/";
private static final String BASE_SCRIPT_PATH = "/sql/UserQuestionCommentRestControllerTest/";
private ExpectationTester expectationTester;
public UserQuestionCommentRestControllerTest() throws Exception {
/* If I understand correctly, here we should have an implicit super() call
and then some reflexion magic to initialize the parent fields. But
that initialization doesn't happen! */
token = testUtil.getToken(testUsername, testPassword); // ← the field in question
}
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
public class IntegrationTestContext {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected TestUtil testUtil; // ← it's autowired
@PersistenceContext
protected EntityManager entityManager;
@RequiredArgsConstructor
@Component
public class TestUtil {
// the entire purpose of this class is to retrieve a JWT token
private final ObjectMapper objectMapper;
private final MockMvc mockMvc;
public String getToken(String username, String password) throws Exception {
MvcResult result = mockMvc.perform(post("/api/v1/token")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(new JwtRequestDto(username, password))))
.andExpect(status().isOk())
.andReturn();
ObjectNode content = objectMapper.readValue(result.getResponse().getContentAsString(), ObjectNode.class);
return "Bearer " + objectMapper.readValue(content.get("data").toString(), String.class);
}
}