[JAVA, TEST] MOCK, MOCKITO 와 SERVICE, CONTROLLER TEST CODE

Mockito, Mock, MockBean, Spy, SpyBean, InjectMocks, MockMvc

Posted by iheese on August 12, 2022 · 8 mins read
  • 실습은 Eclipse STS4에서, JUnit5 기준으로 진행됩니다.


Mock을 사용하는 이유

  • 어떤 메소드, 기능를 위한 작은 단위 테스트를 할 때 서버를 돌려 테스트하는 것은 메모리, 시간 낭비이다.
  • 또한 메소드, 기능이 강한 의존성에 걸쳐 있다면 단위 테스트는 어렵고 복잡해진다.


Mock

  • Mock은 가짜 객체를 의미한다.
  • 개발자에 의해 어떤 값이 입력되면 리턴되는 행위를 정해주어야 한다.

mocking

  • 가짜 객체이므로 테스트가 가벼워지고 빨라진다는 장점이 있다.


Mock 관련 Annotation

  • @ExtendWith(MockitoExtension.class) :
    • 테스트 클래스가 Mockito를 사용한다는 의미이다.
  • @Mock :
    • 실제 객체 대신 가짜 객체로 선언한다는 의미이다. (빈 껍데기 상태)
  • @MockBean :
    • 가짜 객체를 Spring의 Bean으로 등록한다는 의미이다.
    • 스프링 컨테이너를 띄워서 테스트하는 경우 테스트할 대상이 Bean에 의존성을 가지고 있을 때 사용한다.
  • @InjectMocks :
    • @Mock, @Spy으로 선언한 객체를 주입 받을 클래스를 의미한다.
    • 주로 테스트할 클래스를 필드선언할 때 사용된다.


Spy

  • Spy는 Test code 상에서 실제 객체를 똑같이 이용할 수 있게 해준다.
  • 실제 객체를 쓰지않고 Spy를 쓰는 이유는 Mock 객체에서만 사용이 가능한 검증 메소드(verify, assertEquals 등)를 사용할 수 있기 때문이다.
  • 지나치게 사용하면 실제 코드를 이용한 테스트랑 다를 게 없어지기 때문에 적절히 사용하는 것이 좋다.


Spy 관련 Annotaion

  • @Spy :
    • Test Code 에서 실제 객체처럼 사용한다는 의미이다.
  • @SpyBean :
    • Test Code에서 실제 객체처럼 사용할 객체를 Spring의 Bean으로 등록한다는 의미이다.
      • SpyBean은 실제 구현된 객체를 감싸는 프록시 객체 형태이므로, 스프링 컨테이너에 실제 구현체가 등록되어 있어야 한다.


Mockito

  • 단위 테스트를 위한 Java Mocking 프레임워크이다.
  • JUnit 위에서 동작하며 Mock 객체 생성, 객체 행위, 검증까지 처리해준다.

Mockito 사용하기

  • Spring Boot를 사용한다면 spring-boot-starter-test 의존성 내 Mockito가 포함되어 있을 것이다. (mockito-core, mockito-junit-jupiter : JUnit5의 Mockito 확장 구현체)
  • Spring Boot를 사용하지 않는다면

pom.xml(Maven)

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>

build.gradle(Gradle)

testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.6.1'
testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '4.6.1'
  • 프로젝트 상황에 맞게 버전을 변경하여 사용하자.


Mock 사용 예제

  • Spy도 상황에 맞게 Mock과 똑같이 사용하면 되므로 Mock만 정리하겠다.


Service 메소드 테스트로 간단히 알아보는 사용법
  • @ExtendWith(MockitoExtension.class), @Mock, @InjectMocks
  • Service 객체는 스프링 컨테이너를 띄우지 않고 Mock을 이용하여 가볍게 로직을 테스트할 수 있다.
  • memberService.getMember(int memberId) - memberRepository.findById(int memberId) 로 연결되는 상황이다.
@ExtendWith(MockitoExtension.class) //Mockito 사용 선언
public class TestServiceTest {
	
	@InjectMocks //객체 주입받을 테스트할 클래스
	private MemberService memberService;
	
	@Mock //가짜 객체 생성
	private MemberRepository memberRepository;
		
	@DisplayName("Member 객체 조회 서비스 테스트")
	@Test
	void getMemberTest() {
		//given
        //테스트에 사용할 객체 생성
        Member member = Member.builder()
						.memberId(1)
						.memberUsername("test")
						.memberPassword("test111")
						.build();
                        
		//Mock 객체 행위 정의                        
        when(memberRepository.findById(member.getMemberId())).thenReturn(member);
		
        //when
        //memberService가 행동 했을 때
		Member searchedMember = memberService.getMember(member.getMemberId());
		
		//then
        //검증 단계
		verify(memberRepository).findById(member.getMemberId());
		assertEquals(searchedMember, member);
	}
}

코드에 대한 설명

  • @DisplayName : Test에 대한 설명

  • given : 테스트 전 상황 설정, Mock 객체 행위 설정
    • member라는 객체 생성, memberRepository.findById(member.getMemberId()) 메소드가 실행되면 member 객체를 리턴한다.
  • when : 테스트 상황이 일어났을 때
    • memberService.getMember(member.getMemberId())가 사용되면 searchedMember로 정의한다.
  • then : 테스트 검증
    • verify : memberRepository.findById(member.getMemberId()) 가 한 번 잘 실행되었는지 검증한다.
    • assertEquals : searchedMember, member 두 객체가 같은지 확인한다.


  • 성공하면 초록 막대, 실패하면 빨간 막대가 뜬다.
  • Mock 객체의 행위 설정, 검증 방법에 대해서 추후에 다시 다루겠다.


Controller 메소드 테스트로 간단히 알아보는 사용법

  • @MockBean
  • @WebMvcTest(MemberController.class)
    • 웹 어플리케이션을 서버에 올리지 않고 MVC 환경을 만들어 요청, 전송 및 응답을 테스트하게 도와주는 Annotation이다.
    • Controller를 테스트할 때 사용된다.
  • MemberController의 get방식으로 “members/{memberId}” 요청이 들어왔을 때 memeberService.getMember로 연결되는 상황이다.
@WebMvcTest(MemberController.class) //MemberController 테스트 선언
public class MemberControllerTest {
	
	@MockBean //가짜 객체 Bean 등록
	private MemberService memberService;
	
	@Autowired // mockMvc 객체를 주입 받음
	private MockMvc mockMvc;
    
    @DisplayName("Member 객체 조회 컨트롤러 테스트")
	@Test
	void getMemberTest() throws Exception {
		//given
        //테스트에 사용할 객체 생성
		Member searchedMember = Member.builder()
						.memberId(1)
						.memberUsername("test")
						.memberPassword("test111")
						.build();
	
		//MockBean 객체 행위 정의
    	when(memberService.getMember(member.getMemberId())).thenReturn(searchedMember);
		
		//when
        //클라이언트로부터 요청이 왔을 때
		mockMvc.perform(get("/members/"+member.getMemberId()))
		
		//then
				.andExpect(status().isOk()) //200 상태 코드인지
				.andExpect(model().attribute("member", searchedmember)) 
                //model에 값들이 잘 담겼는지
                .andExpect(view().name("member")); //view 생성이 잘되었는지
				.andDO(print()) //요청 응답 정보 콘솔에 출력
 	}
}
코드에 대한 설명
  • MockMvc을 이용하여 Controller 테스트할 때는 의존성이 있는 객체를 모두 MockBean으로 등록해주어야 한다.
  • given : 테스트 전 상황 설정, MockBean 객체 행위 설정
    • 테스트에서 사용할 searchedMember 객체 생성
    • memberService.getMember(member.getMemberId()) 호출될 때 searchedMember 한다.
  • when : 테스트 상황이 일어났을 때
    • mockMvc.perform(get(“/members/{memberId}”))로 get 요청이 들어온 상황을 구현한다.
    • MockHttpServletRequestBuilder를 이용해 상황에 맞게 구현할 수 있다.
  • then : 테스트 검증
    • andExpect로 기대되는 응답을 검증한다.
    • MockMvcResultMatcher를 이용해 더 많은 정보를 검증할 수 있다.


  • 성공하면 초록 막대, 실패하면 빨간 막대가 뜬다.
  • MockMvc 객체의 행위 설정, 검증 방법에 대해서 추후에 다시 다루겠다.


Reference: