[TIL] 250615_Spring: 회원 관리 예제 - 백엔드 개발(3), 의존성 주입

지코·2025년 6월 16일
1

Today I Learned

목록 보기
74/74
post-thumbnail

🍃 회원 서비스 테스트

이제 구현한 회원 서비스를 테스트하는 로직을 구현해야 하는데 ~

IntelliJ에서 테스트 파일을 만드는 단축키는 Command + Shift + T이다.
그럼 'Create New Test' 라는 문구가 뜨는데, 이걸 눌러주면 다음 화면이 뜬다.

테스팅 라이브러리는 기존에 선택되어있는 JUnit으로 하고, 테스트를 진행할 메서드들을 선택해준다. 나는 모든 메서드에 대해 테스트를 진행할 것이므로 전부 선택했다.
이렇게 하면 기존에 테스트 패키지를 생성한 위치(test > java > hello.hello_spring > ...)에 같은 구조로 생성이 된다.

먼저 테스트 로직을 작성할 때 BDD(Behavior-Driven Development) 스타일에 따라, given - when - then 패턴을 사용한다.

  • given: 테스트를 위한 사전 조건(입력값, 환경)을 설정하는 부분.
  • when: 실제로 테스트할 메서드를 실행하는 부분.
  • then: 실행 결과에 대해 검증(Assertion)하는 부분.
@Test
void 회원가입() {
    // given
    Member member = new Member();
    member.setName("spring");

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

    // then
    Member findMember = memberService.findOne(saveId).get();
    assertThat(member.getName()).isEqualTo(findMember.getName()); // 이름을 검증
}

회원가입 메서드는 서비스의 join 메서드를 검증하는 테스트 로직으로, join 메서드를 통해 반환 받은 회원의 id 값으로 회원을 조회했을 때 찾은 회원의 이름과 테스트에서 생성한 회원 객체의 이름이 같은지를 검증한다.

이 부분도 중요하지만, 우리가 테스트 로직을 구현하는 주요 목적은 예외 처리가 잘 되어있는지를 체크하기 위해서다. 따라서 다음과 같은 테스트 로직을 추가한다.

@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));

//    try {
//        memberService.join(member2);
//        fail();
//    } catch (IllegalStateException e) {
//        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//    }

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

MemberService 클래스의 join 함수에는 이름 중복 여부 검사 로직이 존재한다. 이름이 중복될 시 IllegalStateException 에러가 발생하는데, 이 에러가 정상적으로 발생하는지 확인하기 위한 로직을 작성한다.

assertThrows(A, B)
: B 로직을 실행할 경우 A 에러가 발생해야 함.

위 예시를 살펴보자.
"string"이라는 이름을 가진 member1 객체가 DB에 저장된 상태에서 같은 이름을 가진 member2 객체의 join을 시도한다면, 이름 중복 금지 룰에 의해 IllegalStateException 에러가 정상적으로 발생한다.

이는 try-catch문으로도 작성할 수 있지만, assertThrows 함수로 좀 더 간단하게 구현할 수 있다.

마지막으로 발생한 에러의 메세지가 해당 문자열이 맞는지 검증해주면 테스트 로직은 종료된다.


🍃 의존성 주입(Dependency Injection)

서비스 객체가 자신이 의존하는 리포지토리 객체의 구현체를 직접 생성하지 않고, 외부(설정 등)에서 전달받아 사용하는 설계 방식.

🤔 서비스에서 사용하는 DB와 리포지토리에서 사용하는 DB가 다르다면 어떻게 될까❓ 저장한 데이터를 엉뚱한 DB에 가서 찾고 있는 상황이 발생할 수 있다.

이를 위해 MemberService 클래스 내에 생성자를 구현하기로 한다.

public class MemberService {
  private final MemberRepository memberRepository;

  public MemberService(MemberRepository memberRepository) {
      // MemberService 클래스 내에서 리포지토리를 직접 생성하지 않고, 기존에 생성한 리포지토리를 이용하기 위함.
      this.memberRepository = memberRepository;
  }
}

이처럼 생성자를 통해 MemberService 객체를 만들 때마다 MemberRepository 구현체를 인자로 전달해야 하며, 전달된 리포지토리 객체는 MemberService의 필드에 저장되어 이후 서비스 내부에서 사용된다.

이제 테스트에서 이 부분을 적용해보자.

class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
}

@BeforeEach 는 JUnit에서 각 테스트 메서드가 실행되기 전에 자동으로 호출되는 메서드에 사용하는 어노테이션이다. 서비스를 테스트할 때 리포지토리 객체를 하나 만들고, 이를 서비스 객체에 인자로 넣어서 테스트와 서비스가 같은 저장소를 공유하도록 한다.

이를 통해 테스트의 정확도가 증가하며, 나중에 데이터 저장소를 변경할 때에도 유연하게 변경할 수 있다.

Reference

🎥 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

profile
꾸준하고 성실하게, FE 개발자 역량을 키워보자 !

0개의 댓글