관리 메뉴

제뉴어리의 모든것

[Section3] [Spring MVC] 테스팅(Testing) - 4 (Mockito) Service 계층에 Mockito 이용 테스트 본문

코드스테이츠/정리 블로깅

[Section3] [Spring MVC] 테스팅(Testing) - 4 (Mockito) Service 계층에 Mockito 이용 테스트

제뉴어리맨 2022. 9. 18. 13:22

깃 허브 주소 

https://github.com/JanuaryKim/JanuaryKim-be-template-testing

 

GitHub - JanuaryKim/JanuaryKim-be-template-testing: 서비스 계층과 컨트롤러 계층의 슬라이스 테스트

서비스 계층과 컨트롤러 계층의 슬라이스 테스트. Contribute to JanuaryKim/JanuaryKim-be-template-testing development by creating an account on GitHub.

github.com

 

  • build.gradle
plugins {
	id 'org.springframework.boot' version '2.7.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.codestates'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'org.mapstruct:mapstruct:1.4.2.Final'
	annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
	implementation 'org.springframework.boot:spring-boot-starter-mail'
	implementation 'com.google.code.gson:gson:2.9.0'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 

  • 검증하려는 Service 계층의 코드
@Transactional
@Service
public class MemberService {
    private final MemberRepository memberRepository;
    private final ApplicationEventPublisher publisher;

    public MemberService(MemberRepository memberRepository,
                         ApplicationEventPublisher publisher) {
        this.memberRepository = memberRepository;
        this.publisher = publisher;

    }

    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());     // (1)
        Member savedMember = memberRepository.save(member);

        publisher.publishEvent(new MemberRegistrationApplicationEvent(this, savedMember));
        return savedMember;
    }

    ...
		...

    private void verifyExistsEmail(String email) {
        Optional<Member> member = memberRepository.findByEmail(email);  // (2)

        // (3)
        if (member.isPresent())
            throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS);
    }
}

검증하려는 부분은 (1), (2) 를 거쳐서 결국 (3)부분의 로직이다.

즉, 가입 요청을 하는 사람의 이메일과 동일한 이메일을 가진 멤버가 존재하다면 예외를 발생시키겠다는것이다.

 

  • 테스트코드
import com.codestates.exception.BusinessLogicException;
import com.codestates.member.entity.Member;
import com.codestates.member.repository.MemberRepository;
import com.codestates.member.service.MemberService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.BDDMockito.given;

// (1)
@ExtendWith(MockitoExtension.class)
public class MemberServiceMockTest {
    @Mock   // (2)
    private MemberRepository memberRepository;

    @InjectMocks    // (3)
    private MemberService memberService;

    @Test
    public void createMemberTest() {
        // given
        Member member = new Member("hgd@gmail.com", "홍길동", "010-1111-1111");

        // (4)
        given(memberRepository.findByEmail(member.getEmail()))
                .willReturn(Optional.of(member)); // (5)

				// when / then (6)
        assertThrows(BusinessLogicException.class, () -> memberService.createMember(member));
    }
}

(1) : Mockito에서 지원하는 기능을 해당 클래스에서 사용하기 위한 애노테이션

쉽게 말해, org.mockito 패키지의 @Mock, @InjectMocks 같은 애노테이션을 사용하겠다는 것이다.

 

참조 : https://stackoverflow.com/questions/61433806/junit-5-with-spring-boot-when-to-use-extendwith-spring-or-mockito

 

(2) : 가짜 객체를 만든다.

@MockBean과 같이 가짜 객체를 만든다는 점은 같지만,

@MockBean은 @SpringBootTest 와 함께 쓰여야 하며, (현재 코드의 MemberServiceMockTest에서 @MockBean으로 MemberService의 의존성 주입을 받으려고 해도, 객체 조차 만들어지지 않는다)

가짜 객체를 만들어 스프링컨테이너에 빈으로 등록하지만,

@Mock은 그냥 가짜 객체만 만들어 주는것이다.

 

그래서 

@SpringBootTest 환경에서는 @Mock, @MockBean 모두 사용 가능.

@SpringBootTest 아닌 환경에서는 @Mock 가능, @MockBean 불가능이다. (스프링 컨테이너가 존재하지 않으면 @MockBean 으로 객체조차 주입되지 않고 null이 된다)

 

(3) : @Mock 애노테이션이 붙어서 가짜 객체가 만들어진 것들 중 의존성주입에 필요한 객체들을 의존성 주입 받겠다는 것이다.

그러므로, memberService 객체에는 (2)에서 @Mock으로 의존성 주입된 memberRepository가 주입되게 된다.

 

(4), (5) : 아무런 필드와 메소드를 가지지 않은 빈 껍데기인 memberRepository에 findByEmail() 메소드의 Input, Output을 정해준다.

 

(6) : 실제 검증부분으로써 
두번째 인자인 ()-> memberService.createMember(member) 해당 구분 진행시,

BusinessLogicException.class 예외가 발생 될것임을 예상한다는 것이다.