Spring/spring Issue

[Unit Test] Mockito

whyWhale 2021. 7. 1.

[ 마틴 엉클 밥 선생님의 단위테스트 말씀(F.I.R.S.T) ]

 

Fast

  • 단위테스트는 가능한 빠르게 실행되어야 한다.(실행에 있어 느린 테스트는 꺼리게 된다면 잘못 만든 테스트다)
  • @SpringBootTest 어노테이션은 해당 애플리케이션의 모든 빈을 IoC Container에 등록하고 테스트를 진행하므로 테스트다 느려진다.

Independent

  • 단위테스는 객체의 상태, 메소드, 이전 테스트 상태, 다른 메소드의 결과 등에 의존해서는 안된다. 따라서 단위테스트는 어떠한 순서로 실행하더라도 성공해야 한다.
  • 이전에 만든 테스트코드는 Repository들에 의존을 하고 있어 한 번 실행한 뒤에는 이미 중복된 ID가 DB에 존재하기 때문에 실패한다. (@Transacional 로 인해 DB를 롤백하기 때문에 실패하지는 않지만, 테스트 대상이 Repository인지 Service인지 모호하다)

Repeatable

  • 단위테스트는 반복 가능해야 한다.
  • DB에 의존하게 되는 현상과 같이 여러번 실행하는 경우 실패하면 안된다.

Self-validating

  • 단위테스트는 자체 검증이 가능해야 한다.
  • 테스트를 개발자가 직접 수동으로 확인할 필요 없이, Assert문 등에 의해 성공 여부가 결과로 나타나야 한다.

Timely

  • 단위테스트를 통과하는 제품코드가 작성되기 바로전에 단위테스트를 작성해야한다.
  • 만약 TDD를 하고 있다면 적용이 되지만 그렇지 않을 수 있다.

 

[@SpringBootTest란?]

 

  • 통합 테스트를 위한 스프링 부트 전용 테스트 어노테이션이다.
  • 애플리케이션이 실행될 때의 설정을 임의로 바꿔 테스트하는 것이 가능하며, 실제 구동되는 애플리케이션과 동일한 컨텍스트를 가지기 때문에 일대일 대응 수준으로 테스트가 가능하다.
  • 실제 구동되는 애플리케이션과 동일한 컨텍스트를 가진다.
  • 애플리케이션의 설정을 모두 로드하기 때문에 규모가 클수록 느리다.
  • @RunWith(SpringRunner.class)를 무조건 같이 사용해야 한다.

 

 

[ Mockito 란]

 

  • 개발자가 동작을 직접 제어할 수 있는 가짜(Mock)객체를 지원하는 테스트 프레임 워크이다.
  • 여러 객체들 간의 의존성이 존재한다. 예를 들어 Controller -> Service -> Repository 이러한 패러다임의 구조를 갖게 되는데 이러한 의존성으로 인해 단위테스트를 작성하는 것이 어려워진다.
  • 이를 해결하기 위해 가짜 객체를 주입시켜주는 Mockito 라이브러리를 활용할 수 있다.

 

[ Mockito 사용법 ]

1. Mock 객체 의존성 주입

Mockito에서 Mock(가짜) 객체의 의존성 주입을 위해서는 크게 3가지 어노테이션이 사용된다.

  • @Mock: Mock 객체를 만들어 반환해주는 어노테이션
  • @Spy: Stub하지 않은 메소드들은 원본 메소드 그대로 사용하는 어노테이션
  • @InjectMocks: @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션

@Mock는 대체할 모듈위에 붙이고 @InjectMocks는 테스트할 모듈에 붙인다. 그러면 @Mock라는 애노테이션이 붙은 모듈이 @InjectMocks가 붙은 모듈에 주입된다.

 

 

2. Stub

의존성이 있는 객체는 가짜 객체(Mock Object)를 주입하여 어떤 결과를 반환하라고 정해진 답변을 준비시켜야 한다. 그러므로 Mockito 에서는 다음과 같은 Stub메소드를 제공한다.

 

  • doReturn(): Mock 객체가 특정한 값을 반환해야 하는 경우
  • doNothing(): Mock 객체가 아무 것도 반환하지 않는 경우(void)
  • doThrow(): Mock 객체가 예외를 발생시키는 경우
  • given(${주요 테스트 계층에 주입할 가짜 객체 })  // import static org.mockito.BDDMockito.given;
    • willReturn()
    • willThrow()

 

※ 예시를 들자면, Order객체가 외부 결제 API에 의존하고 있다고 가정하자면 이때, Order객체의 주문을 테스트하기 위해 외부 결제 API응답을 기다려야 한다. 따라서 테스트가 독립되지 못하는 데, 외부 결제 API대한 응답을 정해놓고(Stub을 이용하여) 주문이라는 서비스를 독립적으로 테스트 할 수 있다.

 

[ BDDMockito의 BDD(Behavior-Driven Development) ]

1. BDD란?

  • BDD는 Behavior-Driven Development의 약자로 행위 주도 개발을 말한다.
  • 테스트 대상의 상태의 변화를 테스트하는 것이고, 시나리오를 기반으로 테스트하는 패턴을 권장한다.
  • 권장하는 기본 패턴은 Given, When, Then 구조를 가진다.
  • 이는 테스트 대상이 A 상태에서 출발하며(Given) 어떤 상태 변화를 가했을 때(When) 기대하는 상태로 완료되어야 한다(Then).

2. Mockito 와 BBD의 차이는?

  • BDDMockito의 내부 코드를 보면 Mockito을 상속한 클래스임을 알 수 있다. 그리고 동작이나 사용하는 방법 또한 Mockito와 거의 차이가 없다.

 

결과적으로 BDDMockito는 BDD를 사용하여 테스트코드를 작성할 때, 시나리오에 맞게 테스트 코드가 읽힐 수 있도록 도와주는(이름을 변경한) 프레임워크이다.

 

JUnit 4.2 기반.

import jpa.jpa_shop.domain.member.Address;
import jpa.jpa_shop.domain.member.Member;
import jpa.jpa_shop.domain.member.Repository.MemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;

@RunWith(MockitoJUnitRunner.class)
@ExtendWith(MockitoExtension.class)
public class unitUserServiceTest {
    @InjectMocks
    private MemberService memberService;

    @Mock
    private MemberRepository memberRepository;

   @Test
   public void MemberServiceJoin() {
       // given
       Member member = Member.builder().
               name("KIM").
               address(Address.builder().city("Seoul").street("soso street").zipcode("59-1").build())
               .build();
       given(memberRepository.save(any())).willReturn(member);
       given(memberRepository.findById(1L)).willReturn(member);

       // when
       Member joinMember = memberService.Join(member);
       Member findMember = memberRepository.findById(1L);

       // then
       Assertions.assertThat(joinMember.getName()).isEqualTo(findMember.getName());
       Assertions.assertThat(joinMember.getAddress()).isEqualTo(findMember.getAddress());
   }
}

 

 

이 코드는 BDD기반으로 작성하였다.

그러므로 memberservice 레이어만 단위테스트를 진행하여 memberRepository를 의존하지 않는 특징을 갖고 (독립적 테스트) @RunWith(MockitoJUnitRunner.class), @ExtendWith(MockitoExtension.class) 선언을 통해 IoC Container가 생성되지 않는다.(즉 Spring에 의존적이지 않는 것) 그러므로써 빠른 테스트를 제공하는 특징이 있다.

 

 

추가적으로 목객체를(@InjectMocks) 주입받을 때는 Interface는 예외가 발생한다.

org.mockito.exceptions.base.MockitoException: 
Cannot instantiate @InjectMocks field named 'memberService'! Cause: the type 'MemberServiceIFS' is an interface.
You haven't provided the instance at field declaration so I tried to construct the instance.
Examples of correct usage of @InjectMocks:
   @InjectMocks Service service = new Service();
   @InjectMocks Service service;
   //and... don't forget about some @Mocks for injection :)

인스턴스의 생성자를 시도했지만 제공받은 것이 없다고 나와있는데, 독립적인 한 계층만 주입받았기 때문에 되지 않는는다 즉 IoC Container가 빈을 모두 등록하지 않기 때문이다.

 

더보기
Refhttps://woowacourse.github.io/tecoble/post/2020-09-29-compare-mockito-bddmockito/
 

Mockito와 BDDMockito는 뭐가 다를까?

해당 게시글은 JUnit5.x를 기준으로 작성되었습니다. 우아한테크코스 레벨2 미션 중에 의문이 생긴 적이 있었다. 테스트 코드를 작성하려는데 어떤 부분에서는 mock 객체를 을 통해 사용하고, 어떤

woowacourse.github.io

https://mangkyu.tistory.com/145

 

[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 코드 작성법 (3/3)

이번에는 Spring 기반의 웹 애플리케이션에 대해 테스트 코드를 작성해보고자 합니다. 해당 프로젝트의 소스 코드는 여기에서 확인하실 수 있습니다. 1. Mockito 소개 및 사용법 [ Mockito란? ] Mockito는

mangkyu.tistory.com

https://pangtrue.tistory.com/228

 

[Spring Boot] @SpringBootTest

먼저, 테스트를 위한 몇 가지 어노테이션에 대해 살펴보자 어노테이션 설명 Bean @SpringBootTest 통합 테스트, 전체 Bean 전체 @WebMvcTest 단위 테스트, Mvc 테스트 MVC 관련된 Bean @DataJpaTest 단위 테스트,..

pangtrue.tistory.com

https://galid1.tistory.com/772

 

Spring Boot - Service Layer 테스트하기

Spring Boot Service Layer 테스트하기 이번 포스팅에서는 Spring Boot에서 Service Layer를 테스트 하는 방법에 대해 알아보도록 하겠습니다. 1. Test 대상 프로젝트1.1 의존성 (build.gradle)1.2 Class 별 설명M..

galid1.tistory.com

https://dongjuppp.tistory.com/71

 

Spring 단위테스트 - 1

Spring Boot 단위테스트 단위테스트란 앱을 모튤단위로 각각 테스트하는것이다. 스프링에서 단위테스트를 어떤식으로 진행하는지에 대해 적어본다. 우선 단위테스트를 위해 한가지 예시코드를 만

dongjuppp.tistory.com

 

댓글