구글에 생성자 주입이라고 검색하면 수많은 게시글이 나온다.
내용은 대부분 비슷하게,
정도다. 이유로는 컴파일 타임 순환 참조 방지, 객체의 불변성 등등,,, 구구절절 맞는 이야기들이 나온다
그 중에서 , 테스트 코드 작성시 NPE가 발생한다는 점에 대해 곰곰히 생각해봤다.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private MemberService memberService;
public void register(String name) {
userRepository.add(name);
}
}
public class UserServiceTest {
@Test
public void addTest() {
UserService userService = new UserService();
userService.register("Name");
}
}
코드 출처: https://mangkyu.tistory.com/125
위 테스트 코드를 보자. 이 코드는 실행하면 NPE가 발생한다. 왜일까?
이를 알기 위해선 생성자 주입 시점
에 대해 알아야한다.
스프링에서, 객체 (빈)은 언제 만들어질까?
스프링에서 따로 설정하지 않았다면, Bean은 Singleton으로 만들어진다.
이 Bean의 생성 시점은 , Application 시작 -> 스프링 컨테이너 시작 -> 컨테이너에서 모든 빈을 싱글톤으로 생성 && 의존성 주입
과정을 거칠 때 생성된다!
즉, 우리가 @Component
등으로 지정해 빈으로 설정 했을 경우 스프링 컨테이너에서 한번만 만들고, 주입까지 해준다는 뜻이다.
다시 위 코드를 바라보자.
UserService userService = new UserService();
테스트 클래스에서, new를 통해 서비스 클래스를 생성하고 있다.
- 해당 클래스는
@SpringBootTest
애노테이션이 붙지 않아, 순수 자바 코드로 돌아간다.- @SpringBootTest 애노테이션을 붙여 스프링으로 실행하더라도, 컨테이너에서 만든 객체가 아닌 새로운 객체가 생성된다.
- 이들은 직접 생성한 클래스니까 당연히 의존성 주입이 되어있지 않다.
따라서 new로 만든 객체에서, 의존하는 클래스의 메소드가 간접적으로 호출되면 NPE가 발생한다.
그럼 생성자 주입으로 바꿔서 해결하는 방법은 뭘까?
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository){
this.userRepository = userRepository;
}
public void register(String name) {
userRepository.add(name);
}
}
public class UserServiceTest {
@Test
public void addTest() {
UserService userService = new UserService(new UserRepository());
userService.register("Name");
}
}
이렇게 주입 대상 객체를 같이 new를 통해 생성자로 넘겨버리면 된다.
이러면 스프링 부트 어플리케이션과 실행과 무관하게 순수 자바 코드에서도 문제없이 돌아가서, 테스트를 진행 할 수 있게 되었다.