본문 바로가기
개발/스프링

[Spring] 스프링에서의 테스트 코드와 의존성 주입

by 카펀 2022. 3. 5.

의존성 주입 (Dependency Injection; DI)에 대해 글을 작성한 바 있습니다. (글은 여기를 참고)

실제로 의존성 주입이 언제 발생하는지 예시를 하나 들어서 설명해 보고자 합니다.

 

Service 및 테스트 코드 작성

아래와 같은 Service 클라스가 있다고 합시다.

 

MemberService.java

public class MemberService {
	
    private final MemberRepository memberRepository = new MemoryMemberRepository();

    // 회원 가입
    public Long join(Member member) {
        //같은 이름이 있는 중복 회원 X
        validateDuplicatedMember(member); //중복 회원 검증

        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicatedMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

	// 전체 회원 조회
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }

}

 

그리고 이를 테스트하는 코드가 아래와 같이 있습니다.

 

MemberServiceTest.java

class MemberServiceTest {

    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void 회원가입() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertEquals(member.getName(),findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        //then
        assertEquals(e.getMessage(), "이미 존재하는 회원입니다.");

    }
    
}

 

위 코드는 실제로 실행해 보면, 테스트가 잘 통과합니다.

하지만 잘 살펴보면 무언가 문제점이 하나 있다는 것을 확인할 수 있는데요.

 

우선 Service에서는 클라스가 호출되면 memberRepository라는 객체를 새로 하나 생성합니다.

그리고 이를 클라스 내의 메소드에서 사용하고요.

 

다음으로 테스트 코드를 봅시다.

테스트 코드 역시 마찬가지로 memberRepository라는 객체를 새로 하나 생성합니다.

 

여기서 문제점이 발생합니다.

테스트 코드는 실제 컨트롤러의 repository가 아닌, 테스트 코더 내에서 생성된 repository를 사용하여 테스트를 진행합니다.

이러면 지금 당장의 경우에는 문제가 되지 않을 수 있어도, 테스트가 정상적으로 진행되지 않을 여지가 존재합니다.

원칙적으로도 테스트는 실제 사용하는 repository를 이용하여 테스트 하는 것이 맞고요.

 

그렇다면 이걸 어떻게 수정할 수 있을까요?

아래와 같이 컨트롤러와 테스트 코드를 수정해 보았습니다.

 

public class MemberService {

    private final MemberRepository memberRepository;

    // 테스트 코드에서 같은 repository를 사용하기 위해 생성자를 사용
    // (직접 생성하는 것이 아니라 외부에서 넣어주도록)
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    // 회원 가입
    public Long join(Member member) {
        //같은 이름이 있는 중복 회원 X
        validateDuplicatedMember(member); //중복 회원 검증

        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicatedMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

	// 전체 회원 조회
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }

}

 

MemberServiceTest.java

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    // main 코드와 test 코드에서 같은 repository를 사용하기 위함
    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    /**
     * memberService 입장에서 보자.
     * 직접 new 하지 않고, 외부에서 memberRepository를 넣어 준다.
     * 이것을 의존성 주입 (Dependency Injection, DI)라고 한다.
     */

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void 회원가입() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertEquals(member.getName(),findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        //then
        assertEquals(e.getMessage(), "이미 존재하는 회원입니다.");

    }

}

 

테스트 코드를 먼저 보시면, @BeforeEach 어노테이션을 이용하여, 각 테스트 수행 전에 작동할 코드를 적었습니다.

memberRepository 객체를 새로 생성하고, memberService를 선언하며 memberRepository를 주입합니다.

 

Service 코드에서는, Service를 호출한 밖에서 넘겨준 객체를 주입 받습니다.

이것이 의존성 주입 (Dependency Injection; DI)의 좋은 예입니다.

이렇게 되면 테스트 코드에서 생성한 repository를 Service에서 그대로 사용할 수 있고, 그만큼 안정성 있는 테스트를 수행할 수 있게 됩니다.

 

 

물론 테스트도 여전히 통과하고요!

 

 

*이 글은 인프런 - 김영한 님의 '스프링 입문 - 코드로 배우는 스프링 부터, 웹 MVC, DB 접근 기술' 강의를 보고 정리한 내용입니다.

댓글