외부 연동을 다루는 방법

Gyeongjae Ham·2023년 6월 28일
0

TEST

목록 보기
6/7
post-thumbnail

이 시리즈는 TDD를 숙달하기 전에 TEST 자체에 대한 이해를 높이기 위한 학습 시리즈입니다

개선 전 UserService

  @Transactional
    public UserEntity create(UserCreate userCreate) {
        UserEntity userEntity = new UserEntity();
        userEntity.setEmail(userCreate.getEmail());
        userEntity.setNickname(userCreate.getNickname());
        userEntity.setAddress(userCreate.getAddress());
        userEntity.setStatus(UserStatus.PENDING);
        userEntity.setCertificationCode(UUID.randomUUID().toString());
        userEntity = userRepository.save(userEntity);
        String certificationUrl = generateCertificationUrl(userEntity);
        sendCertificationEmail(userCreate.getEmail(), certificationUrl);
        return userEntity;
    }

    private void sendCertificationEmail(String email, String certificationUrl) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(email);
        message.setSubject("Please certify your email address");
        message.setText("Please click the following link to certify your email address: " + certificationUrl);
        mailSender.send(message);
    }

    private String generateCertificationUrl(UserEntity userEntity) {
        return "http://localhost:8080/api/users/" + userEntity.getId() + "/verify?certificationCode=" + userEntity.getCertificationCode();
    }
  • 개선전 UserService에서 개선할 부분만 잘라낸 코드 부분입니다
  • Service 레이어에 이메일에 코드를 전송하고, 인증 URL까지 생성하는 기능이 구현되어 있습니다
    • 그 부분에서 사용된 JavaMailSender를 의존하고 있는 형태입니다
  • 이 부분을 별도의 인증하는 Service로 분리해 보겠습니다
  • 같은 Service 패키지 아래에 CertificationService를 만들고 로직을 옮겨줍니다

  • 해당 로직을 옮기면 UserService는 아래 모습으로 변경되었습니다
    @Transactional
    public UserEntity create(UserCreate userCreate) {
        UserEntity userEntity = new UserEntity();
        userEntity.setEmail(userCreate.getEmail());
        userEntity.setNickname(userCreate.getNickname());
        userEntity.setAddress(userCreate.getAddress());
        userEntity.setStatus(UserStatus.PENDING);
        userEntity.setCertificationCode(UUID.randomUUID().toString());
        userEntity = userRepository.save(userEntity);
        certificationService.send(userCreate.getEmail(), userEntity.getId(), userEntity.getCertificationCode());
        return userEntity;
    }

CertificationService

  • CertificationService로 복잡한 로직을 분리해오긴 했지만, 그대로 코드를 옮긴다면 해당 서비스 레이어가 JavaMailSender에 의존하고 있다는 사실은 변하지 않습니다
  • 의존성 역전으로 의존성을 약하게 만들기 위해서 Interface를 생성해 주겠습니다

MailSender

  • 의존성 역전을 위해서 Interface를 생성하겠습니다
  • Service 레이어에서 중간 다리 역할을 해주고 있으니, port라는 패키지를 만들어서 그 아래에 생성해 주겠습니다
  • MailSender가 가져야하는 send라는 메서드만 정의해 줬습니다
public interface MailSender {

    void send(String email, String title, String content);
}

MailSenderImpl

  • 우리가 생성한 MailSender interface를 구현한 구현체를 생성해줍니다
  • 외부 연동에 관해서 구현된 클래스이므로 infrastructure 패키지 아래에 생성하겠습니다
@Component // component scan 대상인 것을 알려줍니다
@RequiredArgsConstructor
public class MailSenderImpl implements MailSender {

    private final JavaMailSender javaMailSender;

    @Override
    public void send(String email, String title, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(email);
        message.setSubject(title);
        message.setText(content);
        javaMailSender.send(message);
    }
}

CertificationService

  • 자 이제 CertificationService에서 본래 의존하고 있던 JavaMailSender 대신에 우리가 만든 MailSender를 바라보게 만들어줍니다
  • 이 서비스 레이어는 해당 인터페이스를 구현한 구현체만 요구할 뿐 구현체의 자세한 모습을 알지 못하는 형태가 되었습니다. 의존성을 약화시키는 데 성공했습니다
  • 그럼 이 작업으로 인해 테스트에서 어떤 효과를 얻게 되었는지 확인해보도록 하겠습니다
@Service
@RequiredArgsConstructor
public class CertificationService {

    private final MailSender mailSender;

    public void send(String email, long userId, String certificationCode) {
        String certificationUrl = generateCertificationUrl(userId, certificationCode);
        String title = "Please certify your email address";
        String content = "Please click the following link to certify your email address: " + certificationUrl;
        mailSender.send(email, title, content);
    }

    private String generateCertificationUrl(long userId, String certificationCode) {
        return "http://localhost:8080/api/users/" + userId + "/verify?certificationCode=" + certificationCode;
    }
}

Test

  • 우리는 이제 CertificationService에서 MailSender라는 인터페이스를 구현한 구현체만 주입해주면 됩니다
  • 따라서 진짜로 이메일을 전송하는 비즈니스에서 사용할 객체가 아닌 가짜 객체를 쉽게 생성해서 주입해 줄 수 있게 되었습니다
  • 우선 mock이라는 패키지를 만들고 그 아래에 가짜 MailSender 역할을 수행할 클래스를 생성해주겠습니다

FakeMailSender

public class FakeMailSender implements MailSender {
	public String email;
    public String title;
    public String content;
    
    @Override
    public void send(String email, String title, String content) {
    	this.email = email;
        this.title = title;
        this.content = content;
    }
}
  • 그리고 이 가짜 객체를 CertificaitonServiceTest에 사용하도록 하겠습니다
public class CertificationServiceTest {

    @Test
    void 이메일과_컨텐츠가_제대로_만들어져서_보내지는지_테스트한다() {
        // Given
        FakeMailSender fakeMailSender = new FakeMailSender();
        CertificationService certificationService = new CertificationService(fakeMailSender);

        // When
        certificationService.send("kok202@naver.com", 1, "aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");

        // Then
        assertThat(fakeMailSender.email).isEqualTo("kok202@naver.com");
        assertThat(fakeMailSender.title).isEqualTo("Please certify your email address");
        assertThat(fakeMailSender.content).isEqualTo(
                "Please click the following link to certify your email address: http://localhost:8080/api/users/1/verify?certificationCode=aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
        );
    }
}
  • 오늘 다뤄본 예제처럼 외부 연동, 컨트롤러, 서비스를 앞으로 우리가 만들어야 하는 소형 테스트로 다 전환해보도록 하겠습니다
profile
Always be happy 😀

0개의 댓글