[첫번째 프로젝트] 14. Mockito

DAEILLIM·2024년 1월 25일
0

첫번째 프로젝트

목록 보기
14/17
post-thumbnail

Mockito 공식 홈페이지: https://site.mockito.org/
Mockito 깃허브 주소: https://github.com/mockito/mockito

실제 코드에서 Mockito를 적용하기 위해 다음 장의 내용들을 정리했습니다. 따라서 다음 장의 내용에 대해 한 번 읽는 것을 권장합니다. 15. 핵심 코드를 이해하기 위해서는 반드시 선수 지식들을 알고 있어야 합니다.

1. Mockito

  • 단위 테스트를 위해 모의 객체를 생성하고 관리하는 데 사용되는 Java 오픈소스 프레임워크를 의미합니다.
  • 이를 사용하면 실제 객체의 동작을 모방하는 모의 객체(Mock Object)를 생성하여 코드의 ‘특정 부분을 격리’시키고 테스트하기 쉽게 만들어줍니다.
  • 주로 단일 컴포넌트의 동작을 테스트하는 데 사용되며 클래스 내의 개별 메서드나 함수, 서로 다른 클래스 또는 컴포넌트 간의 상호작용, 객체들 간의 협업 등을 테스트할 수 있습니다.

모의 객체(Mock Object)

  • 실제 사용되는 객체 생성을 대체하기 위해 테스트에 사용되는 객체를 의미합니다.
  • 일반적으로 모의 객체의 변수 값은 null, 0, false와 같은 기본 타입의 값이 반환되며 메서드는 기본적으로 null을 구성이 됩니다.

1.1 Mockito를 이용한 테스트 목적

  • Mockito를 사용하여 ‘모의 객체’와 함께 서비스를 호출하여 ‘비즈니스 로직이 올바르게 처리’가 되는지 확인하기 위해 테스트를 수행합니다.
  • 이러한 테스트 과정을 통해 서비스의 비즈니스 로직에 대해 검증하고 예외 상황에 대해 처리를 확인합니다.

2. JUnit5 흐름 / JUnit 5 + Mockito 흐름

2.1 JUnit5 흐름

  • 하단의 그림은 Mockito를 사용하지 않고 JUnit5만을 사용하여서 서비스를 테스트한 테스트 케이스의 흐름을 보여줍니다.
  • 이러한 흐름은 @Test를 수행하는 testsIsNotNullCodeList()라는 메서드에서는 서비스 호출을 위해 ‘Local Server’를 실행시켜서 DB 데이터를 조회해 옵니다.
  • 해당 흐름에서는 매번 데이터 조회를 해오기 위해 ‘로컬 서버’를 실행시켜 테스트를 수행하는 데에 불편함이 발생하고 이를 대처하여 Mockito를 사용합니다.
img

2.2 JUnit 5 + Mockito 흐름

  • 하단의 그림은 Mockito를 사용하여 서비스를 테스트한 테스트 케이스의 흐름을 보여줍니다.
  • 이러한 흐름은 @Test를 수행하는 TestMethod()라는 메서드에서 서비스 호출을 위해 getCodeList() 함수를 호출하지만 DB에서 데이터를 조회해오지 않는 형태입니다.
  • 이 과정에서는 JUnit5만을 이용하였던 방식과 다르게 직접적인 DB 호출을 수행하지 않고 Mock Object라는 모의 객체를 구성하여서 테스트를 진행하는 방식입니다.
image-20240125090925204

2.3 Mockito 수행과정

- 모의 객체 생성 → 메서드 호출 예상 동작 설정 → 메서드 호출 검증

1. 모의 객체 생성 : Mock

  • Mockito를 사용하여 테스트에 필요한 객체의 모의(가짜) 객체를 생성합니다.
  • 이 모의 객체는 실제 객체와 비슷한 행동을 하지만, 프로그래머가 원하는 대로 조작할 수 있습니다.

2. 메서드 호출 예상 동작 설정 : Stub

  • 모의 객체의 메서드 호출에 대한 ‘예상 동작’을 정의합니다.

3. 메서드 호출 검증 : Verify

  • 모의 객체에 대해 특정 메서드가 호출되고 예상된 인자와 함께 호출되었는지를 검증하는 메서드를 제공합니다
// 1. 모의 객체 생성 : Mock
List<String> mockList = Mockito.mock(List.class);

// 2. 메서드 호출 예상 동작 설정 : Stub
Mockito.when(mockList.size()).thenReturn(5);

// 3. 메서드 호출 검증 : Verify
Mockito.verify(mockList).add("item");

2.4 Mockito 주요 용어

용어분류설명
Mock용어실제 객체를 대신하여 프로그래밍을 테스트할 수 있는 모의 객체를 생성하는 것으로
특정 동작이나 결과를 설정하고 검증하기 위해 사용됩니다.
Stub용어특정 메서드 호출에 대해 미리 정의된 동작을 반환하는 객체로,
테스트에서 사용될 때 실제 동작이 아닌 가짜 동작을 수행합니다.
Spy용어실제 객체를 사용하면서 해당 객체의 일부 동작을 감시하고 기록할 수 있는 객체입니다.
Mocking용어특정 동작이나 결과를 시뮬레이션하기 위해
모의 객체를 생성하거나 가짜 동작을 정의하는 것을 말합니다.
Verification용어메서드 호출이나 객체 동작이 예상대로 수행되었는지 확인하는 작업입니다.
Matchers메서드모킹이나 확인 작업에서 사용되는 매개변수 일치 여부를 확인하는 데
사용되는 메서드를 제공합니다.
MockitoAnnotations클래스Mockito 애너테이션을 사용하여 Mock 및 Spy 객체를 초기화하는 데
사용되는 클래스입니다.
MockitoJUnitRunner클래스JUnit 테스트에 Mockito를 사용하는 데 필요한 설정을 자동으로 처리하는
러너 클래스입니다.
MockitoJUnitRunner.Silent클래스MockitoJUnitRunner와 유사하지만, Mock 객체를 생성하지 않은 클래스에서도
실행할 수 있습니다.

2.5 Mock

  • 실제 객체를 대체하는 ‘모의 객체’로 기대하는 동작을 설정하고 검증을 위해 사용이 됩니다.
  • 이러한 Mock로 모의 객체를 생성하고 Stub로 예상 동작을 정의하며 verfiy를 통해 검증을 수행하는 Mockito의 수행과정입니다.
  • 실제 객체의 동작을 제어하는 방식으로 동작을 시뮬레이션하는 객체입니다.
  • 메서드 호출의 예상 동작을 정의하고 올바르게 호출되었는지 확인할 수 있습니다.
// Mock 객체 생성 : Mock - null의 값을 가지는 리스트가 생성됩니다.
List<String> mockList = Mockito.mock(List.class);

// Mock 객체의 동작 정의
Mockito.when(mockList.size()).thenReturn(5);

// Mock 객체 사용
int size = mockList.size(); // 5를 반환

3. Stub

  • 테스트 중에 모의 객체(Mock Object)의 동작을 정의하는 ‘예상 동작’을 설정하는 기능입니다. 이를 사용하여 메서드가 호출될 때 어떤 값을 반환하거나 어떤 예외를 던져야 하는지를 지정할 수 있습니다.
  • 이를 통해 종속성의 동작을 제어하고 테스트 대상을 격리할 수 있습니다.
// Mock 객체 생성
List<String> mockList = Mockito.mock(List.class);

// Mock 객체의 동작 정의 : Stub - Mock 객체의 사이즈를 항상 10으로 반환하도록 예상동작을 설정합니다.
Mockito.when(mockList.size()).thenReturn(10);

// Mock 객체의 메소드 호출
int size = mockList.size();

// 결과 확인
assertEquals(10, size);

4. Spy

  • 기존 객체의 일부 메서드를 원본 동작을 유지하면서 변경하거나 감시할 수 있게 해주는 기능을 제공합니다.
  • Spy를 사용하면 실제 객체를 생성하고 원하는 메서드를 호출할 수 있습니다. 이는 테스트 도중에 객체의 일부 동작을 감시하고, 특정 메서드 호출을 확인하거나 원하는 대로 메서드의 반환 값을 변경할 수 있는 유연성을 제공합니다.
// 실제 객체 생성
List<String> originalList = new ArrayList<>();

// Spy 객체 생성 : Spy - 실제 객체의 원본을 유지하며 객체를 생성합니다.
List<String> spyList = Mockito.spy(originalList);

// Spy 객체의 메서드 동작 정의 : Spy - 이러한 객체에 값을 지정합니다.
Mockito.doReturn("Mocked").when(spyList).get(0);

// Spy 객체 사용
String element = spyList.get(0); // "Mocked"를 반환

5. 모킹

  • 모의 객체의 생성을 통해 실제 객체의 동작을 모방하는 모의 객체를 만드는 과정을 의미합니다.
  • 이러한 모의 객체는 테스트 중인 코드를 격리하고 다양한 시나리오를 시뮬레이션하는 데 사용됩니다.
// 예시 클래스
public class ExampleClass {
    public String getData() {
        // 실제 동작을 하는 메소드
        return "Real data";
    }
}

// Mockito를 사용한 모의 객체 생성 예시
ExampleClass mockExample = Mockito.mock(ExampleClass.class);

// 모의 객체의 동작을 메소드의 반환 값으로 지정
Mockito.when(mockExample.getData()).thenReturn("Mocked data");

// 모의 객체를 사용하여 테스트
String result = mockExample.getData();

// 출력: "Mocked data"
System.out.println(result);

// 모의 객체의 메소드 호출 확인
Mockito.verify(mockExample).getData();

6. 검증

  • 모의 객체의 메서드 호출을 확인하는 프로세스를 의미합니다.
  • 검증(Verification)을 사용하여 특정 메서드가 예상대로 호출되었는지를 확인할 수 있습니다.

ExampleClass라는 예시 클래스의 methodA()methodB()를 모의 객체로 대체하여 해당 메서드의 호출을 확인하고, 호출 순서를 검증할 수 있습니다.

// 예시 클래스
public class ExampleClass {
    public void methodA() {
        // 동작 A
    }

    public void methodB() {
        // 동작 B
    }
}

// Mockito를 사용한 Verification 예시
ExampleClass mockExample = Mockito.mock(ExampleClass.class);

// 메소드 호출
mockExample.methodA();
mockExample.methodB();

// 메소드 호출 확인
Mockito.verify(mockExample).methodA();
Mockito.verify(mockExample).methodB();

// 메소드 호출 순서 확인
InOrder inOrder = Mockito.inOrder(mockExample);
inOrder.verify(mockExample).methodA();
inOrder.verify(mockExample).methodB();

7. Mockito 어노테이션 & 메서드

7.1 Mockito 어노테이션

Mockito에서 제공하는 주요 어노테이션입니다.

Annotation설명
@Mock모의 객체(Mock Object)를 생성하는데 사용됩니다.
@Spy스파이 객체(Spy Object)를 생성하는데 사용됩니다.
@Captor모의 객체에 전달된 메서드 인수를 캡처하는데 사용됩니다.
@InjectMocks테스트 대상이 되는 객체에 ‘모의 객체를 자동으로 주입’할때 사용이 됩니다.
@MockBean스프링 프레임워크에서 사용되며, 테스트용 Mock 객체를 생성하고 주입하는 데 사용됩니다.
@MockitoSettingsMockito의 설정을 변경하거나 커스터마이즈할 때 사용됩니다.
@MockitoJUnitRunnerJUnit 테스트에서 Mockito를 사용하기 위해 실행할 때 사용됩니다.
@BDDMockitoBDD 스타일의 테스트를 위해 Mockito를 사용할 때 사용됩니다.

7.2 @InjectMocks, @Mock, @MockBean 차이점

  • @InjectMocks, @Mock는 Mockito에서 ‘유닛 테스트’에 사용되고 @MockBean는 Spring Boot에서 ‘통합 테스트’에서 사용이 됩니다.
  • @Mock는 특정 클래스나 인터페이스에 대해 ‘모의 객체를 생성’하는 역할을 수행합니다.
  • @InjectMocks는 테스트 대상 객체에 ‘모의 객체를 주입’하는 역할을 수행합니다.

모의 객체를 생성하는 것과 모의 객체를 주입한다의 차이는 무엇인가?

  • @Mock와 같이 ‘모의 객체를 생성’한다는 것은 실제 객체와 동일한 메서드와 동작을 가지지만 실제 데이터나 외부 리소스와의 상호작용은 없습니다.
  • @InjectMocks와 같이 ‘모의 객체를 주입’하는다는 것은 테스트의 대상이 특정 모의 객체를 사용해야 할 때, 그 모의 객체를 자동으로 주입하여 테스트를 수행할 수 있도록 합니다.
  • 모의 객체를 주입하는 것은 @Mock로 생성한 모의 객체가 자동으로 주입되어 테스트가 진행됩니다.
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.MockBean;
import org.mockito.MockitoSettings;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.BDDMockito;
import org.junit.Test;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

public class ExampleClassTest {

    @Mock private Dependency dependencyMock;
    @Spy private Dependency dependencySpy;
    @Captor private ArgumentCaptor<String> captor;
    @InjectMocks private ExampleClass exampleClass;
    @MockBean private ExampleBean exampleBeanMock;
    @MockitoSettings private MockitoSettings settings;
    
    @RunWith(MockitoJUnitRunner.class)
    public class ExampleTestClass {
        
        @Test
        public void exampleTest() {
            // Given
            given(dependencyMock.getSomeValue()).willReturn("Mocked value");

            // When
            String result = exampleClass.doSomething();

            // Then
            verify(dependencySpy).doSomethingElse();
            verify(exampleBeanMock).processValue(captor.capture());
            String capturedValue = captor.getValue();
        }
    }

    public class Dependency {
        public String getSomeValue() {
            return "Real value";
        }

        public void doSomethingElse() {
            // ...
        }
    }

    public interface ExampleBean {
        void processValue(String value);
    }

    public class ExampleClass {
        private Dependency dependency;
        private ExampleBean exampleBean;

        public ExampleClass(Dependency dependency, ExampleBean exampleBean) {
            this.dependency = dependency;
            this.exampleBean = exampleBean;
        }

        public String doSomething() {
            String value = dependency.getSomeValue();
            dependency.doSomethingElse();
            exampleBean.processValue(value);
            return value;
        }
    }
}

7.3 Mockito 메서드

메서드설명
mock(Class class)주어진 클래스의 모의 객체를 생성합니다.
doReturn(T value)모의 객체의 특정 메서드 호출에 대한 반환 값을 정의합니다.
when(T methodCall)주어진 메서드 호출에 대한 스텁(stub)을 정의하여
예상동작을 정의하거나 검증할 수 있도록 합니다.
thenReturn(T value)when() 메서드와 함께 사용하여 특정 메서드 호출에 대한 반환 값을 지정합니다.
given(T methodCall)모의 객체의 메서드 호출 동작을 정의합니다. when() 메서드와 동일한 역할을 합니다
verify(T mock)주어진 모의 객체에 대한 메서드 호출 검증을 수행합니다.
any(Class clazz)주어진 클래스에 대해 임의의 인스턴스를 나타내는 Matcher를 생성합니다.
eq(T value)주어진 값을 기준으로 매처(matcher)를 생성합니다.
value에는 null이나 원시 타입의 값 또는 객체가 포함될 수 있습니다.
verifyNoMoreInteractions(T... mocks)주어진 모의 객체들에 대한 추가적인 상호작용이 없는지 검증합니다.
mocks에는 검증할 모의 객체들의 목록을 전달합니다.

7.4 Mockito 메서드 활용

1. Mocking a class

  • mock() 메서드를 통해서 Class라는 클래스를 Mock 클래스로 생성합니다. 이를 통해 클래스의 가짜 인스턴스를 생성합니다.

2. Setting up method call and return value

  • 가짜 클래스에서 methodCall 메서드가 호출될 때 value라는 값을 반환하도록 설정합니다.

3. Verifying method call

  • mockClass에서 methodCallany(Class.class)eq(value)와 함께 호출되었는지 확인합니다.

4. Verifying no more interactions

  • 추가적인 상호작용이 없는 것을 확인하는 부분입니다. mockClass와 관련하여 추가적인 상호작용이 없는지 확인합니다.
// 1. Mocking a class
Class<T> mockClass = mock(Class.class);

// 2. Setting up method call and return value
T value = mockClass.doReturn(value).when(methodCall).thenReturn(value);

// Verifying method call
verify(mockClass).methodCall(any(Class.class), eq(value));

// Verifying no more interactions
verifyNoMoreInteractions(mockClass);

8. Mockito: Given-When-Then 패턴

Given-When-Then 패턴

  • 테스트 케이스를 더 가독성 있고 유지보수하기 쉽도록 구조화하는 방법을 의미합니다.
  • 이 패턴을 따르면 테스트 케이스가 더 구체적이고 이해하기 쉬워지며 각각의 단계를 분리하여 각 테스트 부분이 어떤 역할을 담당하는지 명확해집니다.
  1. Mockito의 Given 단계

    • *테스트 시나리오에서 ‘사전 조건’을 설정하는 부분입니다.**
    • 즉, 객체의 초기 상태나 호출된 메서드의 입력 값을 설정합니다. 예를 들어, Given 단계에서 모의 객체의 특정 메서드가 호출되었을 때 어떤 값을 반환하도록 설정할 수 있습니다.
  2. Mockito의 When 단계

    • 테스트할 메서드를 호출하는 부분입니다.
    • 이 부분에서는 실제로 테스트 대상 메서드를 호출하여 원하는 ‘동작을 검증’합니다.
  3. Mockito의 Then 단계

    • ‘테스트 결과를 검증’하는 부분입니다.
    • 예상되는 결과를 검증하고, 모의 객체의 메서드가 예상대로 호출되었는지를 확인할 수 있습니다.

8.1 Given-When-Then 패턴 예제1

해당 예시에서는 result 값과 expectedValue 값이 일치하는지를 검증하고 mockObjectsomeMethod()가 예상대로 호출되었는지를 확인하기 위한 예시입니다.

1. Given : 사전 조건

  • mockObjectsomeMethod() 메서드가 호출되었을 때 expectedValue를 반환하도록 설정합니다.

2. When : 동작을 검증

  • testedObjecttestedMethod()를 호출하여 실제로 테스트 대상 메서드를 실행합니다.

3. Then : 테스트 결과 검증

  • resultexpectedValue와 일치하는지를 검증하고, mockObjectsomeMethod()가 예상대로 호출되었는지를 확인합니다.
// Given
Mockito.when(mockObject.someMethod()).thenReturn(expectedValue);

// When
Object result = testedObject.testedMethod();

// Then
assertEquals(expectedValue, result);
Mockito.verify(mockObject).someMethod();

9. Mockito 초기 환경설정

Mockito를 사용하기 위해 Gradle 내에 의존성을 추가하여 Service나 Repository 기반의 테스트 메서드를 구현하기 위한 환경설정에 대해 알아봅니다.

9.1 의존성 설정

테스트하는 환경이 Java 11 버전을 사용하고 있기에 ’mockito-core’ 5.x.x 버전을 의존성으로 주입합니다.
(mockito-core의 버전에 따라 제공하는 Java 버전이 다르기에 하단의 확인해보셔야 합니다)

// [필수] mockito 의존성 추가
testImplementation 'org.mockito:mockito-core:5.8.0'

// [선택] spring-boot-starter-test를 사용하지 않는 경우 추가 필요
testImplementation 'org.mockito:mockito-junit-jupiter:5.8.0'

추가 라이브러리에 대한 설명

라이브러리설명
mockito-core기본 Mocking 및 검증을 위한 핵심 Mockito 라이브러리입니다.
mockito-allMockito의 모든 기능을 포함하고 있는 라이브러리입니다.
(* 단 해당 버전은 2버전까지 해서 종료되었습니다.)
mockito-inline인라인 Mocking을 위한 Mockito 라이브러리입니다.
mockito-junit-jupiterMockito와 JUnit Jupiter를 함께 사용할 수 있도록 지원하는 라이브러리 입니다.
(* 단 spring-boot-starter-test 라이브러리 내에 포함되어 있으니 이를 사용하지 않는 다면 설정이 필요합니다)

mockito-core와 mockito-inline의 차이

mockito-core는 기본적인 Mocking 및 검증을 위한 라이브러리이고, mockito-inline은 인라인 Mocking을 지원하여 테스트의 유연성과 편의성을 높여줍니다.

9.2 Mockito 버전별 Java 버전 요구사항

Mockito 버전요구 Java 버전특징
Mockito 5.xJava 11 이상
Mockito 4.xJava 8 이상deprecated
Mockito 3.xJava 8 이상
Mockito 2.xJava 8 이상
Mockito 1.xJava 5 이상

10. 서비스 테스트를 위한 파일 생성: Go to… - Test 선택

img

10.1 테스트 생성

img

10.2 테스트 생성 파일 확인

img

11. Mockito 단계 별 구성 과정 : Service 테스트

11.1 Mock 초기화

  • Mockito를 사용하여 Mock 객체를 초기화하는 데 사용됩니다.
  • 이를 수행하면 테스트에서 Mock 객체를 사용할 수 있고 테스트를 실행할 때 예상된 동작을 가진 Mock 객체를 사용할 수 있습니다.
초기화 방식사용 버전설명
@ExtendWith(MockitoExtension.class)JUnit 5- Mockito를 사용하여 테스트 클래스를 초기화하는 데 사용됩니다.
- MockitoExtension은 JUnit 5의 확장 기능으로,
Mockito의 기능을 테스트 클래스에 적용합니다.
MockitoAnnotations.initMocks(this)JUnit 4- Mockito를 사용하여 Mock 객체를 초기화하는 데 사용됩니다.
해당 메서드는 deprecated 되었습니다.
- initMocks() 메서드는 목 객체를 주입하고 테스트 클래스의
필드에 할당합니다. 이 방법은 JUnit 4에서 사용됩니다.
MockitoAnnotations.openMocks(this)JUnit 5- Mockito 3.4.0 버전부터 도입된 새로운 초기화 방식입니다.
- openMocks() 메서드는 목 객체를 주입하고 테스트 클래스의
필드에 할당합니다. 이 방법은 JUnit 5와 함께 사용됩니다.

11.2 @ExtendWith(MockitoExtension.class) 예제1

@ExtendWith(MockitoExtension.class)
class CodeMapperTest {

    @Mock
    private CodeMapper codeMapper;

    @InjectMocks
    private CodeService codeService;

    @BeforeEach
    void setUp() {
    }
}

11.3 MockitoAnnotations.initMocks(this) 예제1

class CodeMapperTest {

    @Mock
    private CodeMapper codeMapper;

    @InjectMocks
    private CodeService codeService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }
}

[참고] MockitoAnnotations.initMocks(this)의 경우 deprecated 되어 MockitoAnnotations.openMocks(this);을 사용하기를 권장합니다.

img

12. Mock Annotation

어노테이션사용 타입설명
@MockRepository / Mapper모의 객체(Mock Object)를 생성하는데 사용됩니다.
@InjectMocksService (implements)모의 객체를 생성하고 인스턴스를 주입하는데 사용됩니다.
  • Service 부분을 테스트를 진행한다 가정을 하였을 때 @InjectMocks, @Mock를 활용하여 구성하였습니다.
    1. @InjectMocks를 통해서 객체를 생성하고 의존성을 주입을 합니다.
    2. @Mock를 통해 모의 객체를 구성을 하는데, 이유는 Service 로직 내에 Mapper로 인한 처리를 위해 모의 객체를 구성을 하여 null 오류를 방지합니다.
@ExtendWith(MockitoExtension.class)
class CodeMapperTest {

    @Mock
    private CodeMapper codeMapper;

    @InjectMocks
    private CodeService codeService;

    @BeforeEach
    void setUp() {
    }
}

12.1 Mock @Test 구성

  • 테스트를 하려는 서비스의 메서드 별로 구성을 하였습니다.
@ExtendWith(MockitoExtension.class)
class CodeServiceTest {

    @Mock
    private CodeMapper codeMapper;

    @InjectMocks
    private CodeServiceImpl codeService;

    @BeforeEach
    void setUp() {
        // ...
    }

    @AfterEach
    void tearDown() {
        // ...
    }

    @Test
    void testCodeService() {
    }

    @Test
    void selectCodeList() {
    }
}

13. Mockito 예제: Service 테스트

13.1 Mockito를 통한 Service & Repository 테스트 요약

  1. 해당 테스트를 위해서 Service 구현체와 모의 객체를 구성한 Mapper 간의 비교를 수행합니다.
  2. 가상의 모의 객체의 결과와 Service의 구현체에서 구성한 값이 같으면 통과하게 됩니다.

13.2 Service 주요 메서드와 코드

메서드 명요청설명
selectCodeListCodeDTO코드 리스트를 조회합니다
selectCodeByCdString cd키 값을 기반으로 코드 단건을 조회합니다
insertCodeCodeDTO코드를 등록합니다.
updateCodeCodeDTO코드를 수정합니다
deleteCodeCodeDTO코드를 삭제합니다

13.3 Service 인터페이스 예제1

package com.adjh.multiflexapi.service;

import com.adjh.multiflexapi.model.CodeDto;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public interface CodeService {

    // 코드 리스트 조회
    List<CodeDto> selectCodeList(CodeDto codeDto);

    // 코드 단건 조회
    CodeDto selectCodeByCd(String cd);

    // 코드 생성
    int insertCode(CodeDto codeDto);

    // 코드 수정
    int updateCode(CodeDto codeDto);

    // 코드 삭제
    int deleteCode(CodeDto codeDto);
}

CodeService 인터페이스의 구현체

package com.adjh.multiflexapi.service.impl;

import com.adjh.multiflexapi.common.codes.ErrorCode;
import com.adjh.multiflexapi.config.exception.BusinessExceptionHandler;
import com.adjh.multiflexapi.mapper.CodeMapper;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@Service
public class CodeServiceImpl implements CodeService {

    private final SqlSession sqlSession;

    @Autowired
    public CodeServiceImpl(SqlSession ss) {
        this.sqlSession = ss;
    }

    /**
     * 코드 리스트를 조회합니다.
     *
     * @param codeDto CodeDto
     * @return List<CodeDto>
     */
    @Transactional(readOnly = true)
    public List<CodeDto> selectCodeList(CodeDto codeDto) {
        CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
        return cm.selectCodeList(codeDto);
    }

    /**
     * 코드 키 값을 기반으로 코드 정보를 조회합니다
     *
     * @param cd String
     * @return CodeDto
     */
    @Transactional(readOnly = true)
    public CodeDto selectCodeByCd(String cd) {
        CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
        return cm.selectCodeByCd(cd);
    }

    /**
     * 코드 리스트 조회
     *
     * @param codeDto 코드
     * @return CodeDto
     */
    @Transactional(readOnly = true)
    public CodeDto selectCode(CodeDto codeDto) {
        CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
        return cm.selectCode(codeDto);
    }

    /**
     * 코드를 추가합니다.
     *
     * @param codeDto CodeDto
     * @return int
     */
    @Transactional
    public int insertCode(CodeDto codeDto) {
        CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
        try {
            return cm.insertCode(codeDto);
        } catch (Exception e) {
            throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.INSERT_ERROR);
        }
    }

    /**
     * 코드 삭제
     *
     * @param codeDto CodeDto
     * @return int
     */
    @Transactional
    public int deleteCode(CodeDto codeDto) {
        CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
        try {
            return cm.deleteCode(codeDto);
        } catch (Exception e) {
            throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.INSERT_ERROR);
        }
    }

    /**
     * 코드 수정 : 키 값 기반
     *
     * @param codeDto CodeDto
     * @return int
     */
    @Transactional
    public int updateCode(CodeDto codeDto) {
        try {
            CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
            // 1. 코드의 키값을 기반으로 데이터를 조회합니다.
            CodeDto selectCodeByCd = cm.selectCodeByCd(codeDto.getCd());

            // 3. 최종적으로 값을 수정합니다.
            return cm.updateCode(selectCodeByCd);
        } catch (Exception e) {
            throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.UPDATE_ERROR);
        }
    }
}

14. Controller 테스트 구조 및 사용 예시

14.1 Service(구현체) : 조회 테스트(요청 파라미터 전달)

  • 해당 테스트의 요점은 가상의 모의 객체를 만들어서 service에 처리된 결과를 비교하는 서비스를 테스트하기 위함입니다.

1. @ExtendWith(MockitoExtension.class)
- JUnit5와 Mockito를 함께 사용하기 위한 어노테이션을 선언합니다.

2. @Mock
- 모의 객체로 사용할 sqlSession와 codeMapper를 선언합니다.

3. @InjectMocks
- 실제 테스트를 수행할 ServiceImpl로 객체를 생성하고 의존성 주입을 하기 위해 어노테이션으로 선언합니다.

4. @Test
- 실제 처리되는 테스트가 수행되는 메서드입니다.

  1. 가상의 객체를 구성합니다.
  2. 가상의 codeMapper의 메서드를 호출하여 가상의 객체로 반환하도록 구성합니다.
  3. 가상의 sqlSession에 Mapper를 지정해 줍니다.
    (* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
  4. 테스트 대상 메서드를 호출합니다.
  5. 가상의 Mapper 값과 테스트 대상 메서드를 비교합니다.
/**
 * CodeService 구현체를 테스트를 수행합니다.
 *
 * @author : lim
 * @fileName : CodeServiceTest
 * @since : 01/25/24
 */
@ExtendWith(MockitoExtension.class)
class CodeServiceTest {
    
    @Mock private SqlSession sqlSession;
    @Mock private CodeMapper codeMapper;
    @InjectMocks private CodeServiceImpl codeService;

    @BeforeEach
    void setUp() {
        //
    }

    @Test
    void testCodeService() {

        // *************** given : 사전 조건 ******************
        // 1. 가상의 객체를 구성합니다.
        CodeDto mockCodeDto = CodeDto.builder().cd("java").build();

        // *************** when : 동작 검증 ******************
        // 2. 가상의 codeMapper의 메서드를 호출하여 가상의 객체로 반환하도록 구성
        Mockito.when(codeMapper.selectCodeByCd("java")).thenReturn(mockCodeDto);
        
        // 3. 가상의 sqlSession에 Mapper를 지정해줍니다. 
	    // (* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
        Mockito.when(sqlSession.getMapper(CodeMapper.class)).thenReturn(codeMapper);
       
        // 4. 테스트 대상 메서드를 호출합니다.
        CodeDto result = codeService.selectCodeByCd("java");

        // *************** when : 결과 검증 ******************
        // 5. 가상의 Mapper 값과 테스트 대상 메서드를 비교합니다.
        Assertions.assertEquals(mockCodeDto, result);
    }
}

[실행 결과] 아래와 같은 형태로 테스트가 성공함을 확인하였습니다.

img

14.2 Service(구현체) : 조회 테스트(객체 전달)

해당 테스트의 요점은 가상의 모의 객체를 만들어서 service에 처리된 결과를 비교하는 서버스를 테스트하기 위함입니다.

1. @ExtendWith(MockitoExtension.class)
- JUnit5와 Mockito를 함께 사용하기 위한 어노테이션을 선언합니다.

2. @Mock
- 모의 객체로 사용할 sqlSession와 codeMapper를 선언합니다.

3. @InjectMocks
- 실제 테스트를 수행할 ServiceImpl로 객체를 생성하고 의존성 주입을 하기 위해 어노테이션으로 선언합니다.

4. @Test
- 실제 처리되는 테스트가 수행되는 메서드입니다.

  1. 가상의 객체를 구성합니다.
  2. Service와 비교할 반환값을 구성합니다.
  3. Mapper에 가상의 객체를 넣고 반환되는 값을 expectedCodeList 값으로 지정합니다.
  4. 가상의 sqlSession에 Mapper를 지정해 줍니다.
    (* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
  5. 테스트 대상 메서드를 호출합니다.
  6. 반환된 코드 리스트가 예상한 값과 일치하는지 검증합니다.
/**
 * CodeService 구현체를 테스트를 수행합니다.
 *
 * @author : lim
 * @fileName : CodeServiceTest
 * @since : 01/25/24
 */
@ExtendWith(MockitoExtension.class)
class CodeServiceTest {

    @Mock
    private SqlSession sqlSession;

    @Mock
    private CodeMapper codeMapper;

    @InjectMocks
    private CodeServiceImpl codeService;

    @BeforeEach
    void setUp() {
        //
    }

    @Test
    @DisplayName("객체 값을 기반으로 여러개의 코드 값들을 조회합니다.")
    void selectCodeList() {

        // *************** given : 사전 조건 ******************
        // 1. 가상의 객체를 구성합니다.
        CodeDto mockCodeDto = CodeDto.builder().cd("java").build();

        // 2. Service와 비교할 반환값을 구성합니다.
        List<CodeDto> expectedCodeList = new ArrayList<>();

        // *************** when : 동작 검증 ******************
        // 3. 가상의 codeMapper의 메서드를 호출하여 가상의 객체로 반환되도록 지정합니다.
        Mockito.when(codeMapper.selectCodeList(mockCodeDto)).thenReturn(expectedCodeList);

        // 4. 가상의 sqlSession에 Mapper를 지정해줍니다. 
	   // (* 해당 부분은 서비스 구현체(ServiceImpl)에서 사용되는 데이터를 구성하였습니다)
        Mockito.when(sqlSession.getMapper(CodeMapper.class)).thenReturn(codeMapper);

        // 5. 테스트 대상 메서드를 호출합니다.
        List<CodeDto> actualCodeList = codeService.selectCodeList(mockCodeDto);

        // *************** when : 결과 검증 ******************
        // 6. 반환된 코드 리스트가 예상한 값과 일치하는지 검증합니다.
        Assertions.assertEquals(expectedCodeList, actualCodeList);
    }
}

[실행 결과] 아래와 같은 형태로 테스트가 성공함을 확인하였습니다.

img
profile
필기하고, 타이핑하고, 말하면서 읽고, 코딩하고, 눈으로 읽고 오감으로 공부하기

0개의 댓글