Java - Mockito의 @Mock, @Spy, @Captor, @InjectMocks

JS · 27 Sep 2020

Mockito는 Java에서 인기있는 Mocking framework입니다.

이 글에서는 Mockito의 Annotation, @Mock, @Spy, @Captor, @InjectMocks를 사용하는 방법에 대해서 알아봅니다.

Annotation을 사용하기 위한 설정

Mockito 라이브러리에서 @Mock 등의 Annotation들을 사용하려면 설정이 필요합니다. 만약 이런 설정 없이 @Mock 등을 사용한다면 NullPointerException이 발생합니다.

아래에서 소개하는 방법 중 하나를 선택하셔서 테스트 코드에 적용하시면 됩니다.

1. MockitoJUnit.rule

다음과 같이 @Rule을 정의하시면 @Mock 등의 Annotation을 사용할 수 있습니다. 가능하다면 이 방법을 사용하시는 것이 좋습니다.

public class MockitoTest {
    @Rule
    public MockitoRule initRule = MockitoJUnit.rule();
    ....
}

2. MockitoAnnotations.initMocks

다음과 같이 테스트가 실행되기 전에 initMocks(this)를 호출하는 방법도 있습니다.

public class MockitoTest {
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }
}

3. MockitoJUnitRunner

다음과 같이 @RunWith에 MockitoJUnitRunner로 설정하는 방법도 있습니다. 이 방법을 사용하면, 다른 JUnitRunner를 사용하지 못하기 때문에 위의 두가지 방법 중에 하나를 선택하시는 것이 좋습니다.

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {
    ...
}

위의 3가지 중에 하나를 테스트 코드에 적용하셨다면, 테스트 코드에서 @Mock 등의 Annotation을 사용할 수 있습니다.

@Mock

먼저 Annotation을 사용하지 않는 방법을 소개하고, 그 뒤에 Annotation을 사용하는 방법을 소개는 순서로 글을 작성하였습니다.

@Mock을 사용하지 않고 mock 객체 생성

아래 코드는 @Mock을 사용하지 않고 mock 객체를 만드는 코드입니다.

@Test
public void mockingList_verify() {
    List mockList = mock(ArrayList.class);

    mockList.add("apple");
    verify(mockList).add("apple");
}

@Mock으로 mock 객체 생성

다음과 같이 @Mock을 사용하여 mock 객체를 만들 수 있습니다. @Mock을 사용하면 더 적은 코드로 mock 객체를 만들 수 있습니다.

@Mock
List mockList;

@Test
public void mockingList_verify() {
    mockList.add("apple");
    verify(mockList).add("apple");
}

다음은 when으로 stubbing하는 예제입니다.

@Mock
List mockList;

@Test
public void mockingList_when() {
    when(mockList.get(0)).thenReturn("apple");
    when(mockList.get(1)).thenReturn("kiwi");
    when(mockList.size()).thenReturn(10);

    assertEquals("apple", mockList.get(0));
    assertEquals("kiwi", mockList.get(1));
    assertEquals(10, mockList.size());
}

@Spy

다음은 @Spy 없이 spy 객체를 만드는 방법입니다.

@Test
public void spyingList_verify() {
    List spyList = spy(ArrayList.class);

    spyList.add("apple");
    verify(spyList).add("apple");
}

@Spy로 spy 객체 생성

다음과 같이 @Spy를 사용하여 spy 객체를 만들 수 있습니다.

@Spy
List spyList;

@Test
public void spyingList_verify() {
    spyList.add("apple");
    verify(spyList).add("apple");
}

다음은 when으로 stubbing하는 예제입니다.

@Spy
List spyList;

@Test
public void spyingList_when() {
    doReturn("apple").when(spyList).get(0);
    doReturn("kiwi").when(spyList).get(1);
    doReturn(10).when(spyList).size();

    assertEquals("apple", spyList.get(0));
    assertEquals("kiwi", spyList.get(1));
    assertEquals(10, spyList.size());
}

@Captor

다음은 @Captor없이 ArgumentCaptor를 사용하는 코드입니다.

@Test
public void captorExample() {
    ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

    mockList.add("apple");
    verify(mockList).add(arg.capture());

    assertEquals("apple", arg.getValue());
}

@Captor로 ArgumentCaptor 객체 생성

다음과 같이 @Captor를 사용하여 ArgumentCaptor 객체를 생성할 수 있습니다.

@Captor
ArgumentCaptor argCaptor;

@Test
public void captorAnnotationExample() {
    mockList.add("apple");
    verify(mockList).add(argCaptor.capture());

    assertEquals("apple", argCaptor.getValue());
}

@InjectMocks

다음은 이번 예제에서 사용되는 두개의 클래스, Contacts와 ContactsDatabase입니다.

public class Contacts {
    private ContactsDatabase db;

    public Contacts() {
        db = new ContactsDatabase();
    }

    public Contacts(ContactsDatabase db) {
        this.db = db;
    }

    public String getUserInfo(int id) {
        String userInfo= "{ " + db.getName(id) + ", "
                + db.getPhoneNumber(id) + " }";
        return userInfo;
    }

    public void addUserInfo(String name, String number) {
        db.addUserInfo(name, number);
    }
}
public class ContactsDatabase {
    List<String> names = new ArrayList<>();
    List<String> numbers = new ArrayList<>();

    public ContactsDatabase() {
        names.add("AAA");
        numbers.add("000-1234");
    }

    public String getName(int id) {
        return names.get(id);
    }

    public String getPhoneNumber(int id) {
        return numbers.get(id);
    }

    public void addUserInfo(String name, String number) {
        names.add(name);
        numbers.add(number);
    }
}

Mock 객체 주입(Injection)

Contacts 클래스 안에는 ContactsDatabase 객체가 있습니다. Contacts 객체를 테스트하고 싶은데 ContactsDatabase와 의존성이 있어서 테스트가 쉽지 않습니다. 그래서 ContactsDatabase는 Mock으로 만들어 Contacts의 기능을 테스트하려고 합니다.

다음과 코드와 같이 먼저 ContactsDatabase는 Mock 객체로 만듭니다. 그리고 Contacts의 인자로 Mock 객체를 전달하면 Contacts는 실제 객체이지만 내부의 ContactsDatabase 객체는 Mock 객체가 됩니다.

Mock 객체에 when으로 원하는 값이 리턴되도록 만들면, 특정 상황에서 Contacts이 어떻게 동작하는지 테스트할 수 있습니다.

@Mock
ContactsDatabase db;

@Test
public void injectingMock() {
    Contacts contacts = new Contacts(db);
    when(db.getName(0)).thenReturn("JS");
    when(db.getPhoneNumber(0)).thenReturn("100-1234-5678");

    assertEquals("{ JS, 100-1234-5678 }", contacts.getUserInfo(0));
}

@InjectMocks으로 Mock 객체 주입

위의 예제는 Mock 객체를 Contacts에 주입(Injection)하기 위해 Contacts(ContactsDatabase db)라는 생성자를 만들었습니다.

@InjectMocks을 사용하면 생성자를 만들지 않고도 Mock 객체를 주입할 수 있습니다.

먼저 다음과 같이 Contacts(ContactsDatabase db) 생성자를 삭제합니다.

public class Contacts {
    private ContactsDatabase db;

    public Contacts() {
        db = new ContactsDatabase();
    }
    ....
}

다음과 같이 Contacts를 @InjectMocks와 함께 Member 변수로 정의하면, @Mock으로 생성된 객체들이 Contacts에 주입(Injection)됩니다.

@Mock
ContactsDatabase db;

@InjectMocks
Contacts contacts = new Contacts();

@Test
public void injectingMock() {
    when(db.getName(0)).thenReturn("JS");
    when(db.getPhoneNumber(0)).thenReturn("100-1234-5678");

    assertEquals("{ JS, 100-1234-5678 }", contacts.getUserInfo(0));
}

실행해보면, 위의 예제와 동일하게 동작하는 것을 알 수 있습니다.

Spy 객체에 Mock 객체 주입

Spy 객체에 Mock을 주입할 떄는 @InjectMocks를 사용할 수 없습니다. 객체를 생성할 때 생성자에 Mock을 인자로 전달하는 방법을 사용해야 합니다.

다음과 같이 spy()에 ContactsDatabase의 Mock 객체가 주입된 Contacts 객체를 인자로 전달하여 spy 객체를 만들 수 있습니다.

@Mock
ContactsDatabase db;

@Test
public void injectingMockIntoSpy() {
    Contacts contacts = spy(new Contacts(db));

    doReturn("JS").when(db).getName(0);
    doReturn("100-1234-5678").when(db).getPhoneNumber(0);

    assertEquals("{ JS, 100-1234-5678 }", contacts.getUserInfo(0));
}

정리

@Mock, @Spy, @Captor, @InjectMocks를 사용하는 방법에 대해서 알아보았습니다. 이 Annotation을 사용하면 더 적은 코드로 테스트 코드를 작성할 수 있습니다.

댓글을 보거나 쓰려면 이 버튼을 눌러주세요.
codechachaCopyright ©2019 codechacha