티스토리 뷰

Java

[Java] 단위 테스트 Mockito Framework

snail voyager 2023. 8. 21. 23:36
728x90
반응형

Mokito Framework

Mockito는 Java에서 Mock 객체를 만들고 테스트하는 데 사용되는 인기있는 프레임워크

다른 클래스에 대한 가짜(Mock) 객체를 만들고, 해당 객체가 어떻게 동작해야 하는지를 정의하고, 

테스트 중에 해당 객체의 동작을 검증

Mockito는 테스트 주도 개발(Test Driven Development, TDD) 및 단위 테스트 작성을 보다 쉽고 효율적으로 만들어줍니다.

JUnit과 같은 테스트 프레임워크와 함께 사용

 

  • Mock 객체 생성: Mockito를 사용하여 다른 클래스의 가짜(Mock) 객체를 생성할 수 있습니다.
  • 동작 정의: Mockito를 사용하여 Mock 객체의 특정 메서드 호출에 대한 동작을 정의할 수 있습니다. 예를 들어, 특정 메서드가 호출될 때 반환해야 하는 값을 지정하거나, 예외를 던져야 할 때 그러한 동작을 설정할 수 있습니다.
  • 메서드 호출 검증: Mockito를 사용하여 Mock 객체의 메서드 호출이 특정 조건에 따라 예상대로 이루어졌는지를 검증할 수 있습니다. 예를 들어, 메서드가 정확히 한 번 호출되었는지, 특정 매개변수와 함께 호출되었는지 등을 검증할 수 있습니다.
  • Spy 객체: Mockito를 사용하여 실제 객체의 일부를 Mock 객체로 변환할 수 있습니다. 이것을 통해 일부 메서드의 동작을 변경하거나, 메서드 호출을 감시하고 싶을 때 유용합니다.
  • 다양한 기능 제공: Mockito는 많은 다른 기능도 제공합니다. 예를 들어, 인자 일치자(argument matchers)를 사용하여 메서드 호출에 대한 인자를 유연하게 처리할 수 있으며, Stubbing, Verification 등의 기능을 통해 테스트 코드를 작성하고 실행할 수 있습니다.

@Mock

  • 모의 객체(Mock)를 생성할 때 사용
  • Mockito는 해당 필드에 대한 모의 객체를 생성하고 주입
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

import static org.mockito.Mockito.when;

public class MyServiceTest {

    @Mock
    private MyRepository myRepository;	//모의 객체

    @Test
    public void testSomeMethod() {
        when(myRepository.getData()).thenReturn("Mocked Data");	//모의 객체의 동작을 설정

        // 테스트 코드 작성
    }
}

Mock 객체 주입하기

  • Mockito 3.4.0 버전 이후부터는 MockitoAnnotations.initMocks(this) 대신에 MockitoAnnotations.openMocks(this) 메서드를 사용
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class MyServiceTest {

    private MyService myService;

    @Mock
    private MyRepository myRepository;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
        myService = new MyService(myRepository); // 모의 객체를 주입
    }

    @Test
    public void testSomeMethod() {
        // 테스트 코드 작성
    }
}

@InjectMocks 로 Mock 객체 주입하기

  • JUnit 5부터는 @RunWith 어노테이션 대신에 @ExtendWith 어노테이션을 사용
  • @ExtendWith(MockitoExtension.class) 을 사용하여 테스트 클래스에 Mockito 기능을 확장
  • Mockito 라이브러리에서 제공하는 MockitoExtension을 사용하여 Mockito 관련 기능을 확장
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;

@ExtendWith(MockitoExtension.class)
public class MyServiceTest {

    @InjectMocks
    private MyService myService;	// 모의 객체가 주입될 대상

    @Mock
    private MyRepository myRepository;	// 모의 객체 생성

    @Test
    public void testSomeMethod() {
        // 테스트 코드 작성
    }
}

Mockito.when

Mock 객체의 메서드 호출에 대한 동작을 정의하는 데 사용

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

public class CalculatorTest {
    @Mock
    private Calculator calculatorMock; // Calculator 클래스의 목 객체 생성

    @Test
    public void testAddition() {
        // add() 메서드가 호출될 때 2와 3을 전달 받으면 5를 리턴하도록 설정
        when(calculatorMock.add(2, 3)).thenReturn(5);

        // 목 객체를 사용하여 테스트
        int result = calculatorMock.add(2, 3);

        // 결과 확인
        assertEquals(5, result);
    }
}

thenReturn(): 메서드 호출이 발생했을 때 목 객체가 반환할 값을 지정하는 데 사용
thenThrow(): 메서드 호출이 발생했을 때 목 객체가 예외를 던지도록 설정하는 데 사용
thenAnswer(): 메서드 호출이 발생했을 때 사용자가 정의한 동적 로직에 따라 동작하도록 설정하는 데 사용

import static org.mockito.Mockito.*;

public class ExampleTest {
    @Mock
    private Calculator calculatorMock; // Calculator 클래스의 목 객체 생성

    @Test
    public void testWhenThenReturn() {
        // add 메서드 호출시 2와 3이 들어올 경우 5를 리턴하도록 설정
        when(mockExample.add(2, 3)).thenReturn(5);

        // 테스트
        assertEquals(5, mockExample.add(2, 3));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testWhenThenThrow() {
        // divide 메서드 호출시 0이 들어올 경우 IllegalArgumentException을 던지도록 설정
        when(mockExample.divide(anyInt(), eq(0))).thenThrow(new IllegalArgumentException());

        // 테스트
        mockExample.divide(10, 0);
    }

    @Test
    public void testThenAnswer() {
        // when - thenAnswer 사용하여 동적으로 값을 계산
        when(mockExample.calculate(anyInt())).thenAnswer(invocation -> {
            int argument = invocation.getArgument(0);
            return argument * 2;
        });

        // 테스트
        assertEquals(10, mockExample.calculate(5));
    }
}

Argument Matchers

목 객체의 메서드 호출에 대한 인자를 유연하게 처리

특정한 값을 정확하게 지정하지 않고도 메서드 호출을 검증하거나 설정

  • any(Class<T> type): 특정 클래스 타입의 어떤 인자든 일치합니다.
  • anyInt(), anyLong(), anyDouble(), 등: int, long, double 등의 기본 데이터 타입에 대한 어떤 값이든 일치합니다.
  • eq(T value): 지정된 값과 동일한 인자와 일치합니다.
  • isNull(): null 값과 일치합니다.
  • notNull(): null이 아닌 값과 일치합니다.
  • anyString(), anyListOf(Class<T> clazz), 등: String, List 등 특정 타입의 객체와 일치합니다.
  • argThat(ArgumentMatcher<T> matcher): 사용자가 정의한 조건에 따라 일치하는 인자와 일치합니다.

https://javadoc.io/static/org.mockito/mockito-core/5.11.0/org/mockito/ArgumentMatchers.html

 //stubbing using built-in anyInt() argument matcher
 when(mockedList.get(anyInt())).thenReturn("element");

 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 when(mockedList.contains(argThat(isValid()))).thenReturn(true);

 //following prints "element"
 System.out.println(mockedList.get(999));

 //you can also verify using an argument matcher
 verify(mockedList).get(anyInt());

 //argument matchers can also be written as Java 8 Lambdas
 verify(mockedList).add(argThat(someString -> someString.length() > 5));

any(), anyInt() matchers는 타입 체크를 하기 때문에 null 인자는 isNull()을 사용해야 한다.

 // stubbing using anyBoolean() argument matcher
 when(mock.dryRun(anyBoolean())).thenReturn("state");

 // below the stub won't match, and won't return "state"
 mock.dryRun(null);

 // either change the stub
 when(mock.dryRun(isNull())).thenReturn("state");
 mock.dryRun(null); // ok

 // or fix the code ;)
 when(mock.dryRun(anyBoolean())).thenReturn("state");
 mock.dryRun(true); // ok

argument matchers 사용한다면 메서드의 모든 인자는 matchers를 사용해야 한다.

 verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
 //above is correct - eq() is also an argument matcher

 verify(mock).someMethod(anyInt(), anyString(), "third argument");
 //above is incorrect - exception will be thrown because third argument is given without argument matcher.

Additional matchers

argument matchers를 좀 더 복잡한 일치 조건을 정의하거나, 특정한 상황에 매칭을 수행할 때 유용

https://javadoc.io/static/org.mockito/mockito-core/5.11.0/org/mockito/AdditionalMatchers.html

   //anything but not "ejb"
   mock.someMethod(not(eq("ejb")));

   //not "ejb" and not "michael jackson"
   mock.someMethod(and(not(eq("ejb")), not(eq("michael jackson"))));

   //1 or 10
   mock.someMethod(or(eq(1), eq(10)));

Mockito.verify 호출 횟수

목 객체의 메서드 호출을 검증하는 데 사용

목 객체의 특정 메서드가 지정된 횟수와 매개변수로 호출되었는지를 확인하는 데 유용

기본값은 times(1)로 한 번 호출되었는지를 검증

 //using mock
 mockedList.add("once");

 mockedList.add("twice");
 mockedList.add("twice");

 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 //following two verifications work exactly the same - times(1) is used by default
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 //exact number of invocations verification
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 //verification using never(). never() is an alias to times(0)
 verify(mockedList, never()).add("never happened");

 //verification using atLeast()/atMost()
 verify(mockedList, atMostOnce()).add("once");
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

Mockito.verify 호출 순서 InOrder

여러 목 객체가 순서대로 호출되었는지를 검증할 때 사용

https://javadoc.io/static/org.mockito/mockito-core/5.11.0/org/mockito/InOrder.html

 //Multiple mocks that must be used in a particular order
 List firstMock = mock(List.class);
 List secondMock = mock(List.class);

 //using mocks
 firstMock.add("was called first");
 secondMock.add("was called second");

 //create inOrder object passing any mocks that need to be verified in order
 InOrder inOrder = inOrder(firstMock, secondMock);

 //following will make sure that firstMock was called before secondMock
 inOrder.verify(firstMock).add("was called first");
 inOrder.verify(secondMock).add("was called second");

연속적으로 Stubbing 하기

 when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
   .thenReturn("foo");

 //First call: throws runtime exception:
 mock.someMethod("some arg");

 //Second call: prints "foo"
 System.out.println(mock.someMethod("some arg"));

 //Any consecutive call: prints "foo" as well (last stubbing wins).
 System.out.println(mock.someMethod("some arg"));
 
 
  when(mock.someMethod("some arg"))
   .thenReturn("one", "two", "three");

실행되지 않았는지 확인하기 never()

//using mocks - only mockOne is interacted
 mockOne.add("one");

 //ordinary verification
 verify(mockOne).add("one");

 //verify that method was never called on a mock
 verify(mockOne, never()).add("two");

 

 

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

https://www.baeldung.com/mockito-annotations

728x90
반응형
반응형
300x250