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

Mockito is a popular mocking framework in Java.

In this article, we will learn how to use Mockito Annotation, @Mock, @Spy, @Captor, and @InjectMocks.

Setting to use Annotation

Setting is required to use annotations such as @Mock in the Mockito library. If @Mock is used without this setting, NullPointerException will occur.

You can select one of the methods introduced below and apply it to the test code.

1. MockitoJUnit.rule

If you define @Rule as follows, you can use annotations such as @Mock. We recommend that you use this method whenever possible.

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

2. MockitoAnnotations.initMocks

Another way is to call initMocks(this) before the test is run, like this:

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

3. MockitoJUnitRunner

There is also a way to set it to MockitoJUnitRunner in @RunWith like this: If you use this method, it is better to choose one of the above two methods because other JUnitRunners cannot be used.

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

If one of the three above is applied to the test code, you can use annotations such as @Mock in the test code.

@Mock

I wrote an article in the order of introducing the method not to use Annotation first, and then the method to use Annotation.

Create mock object without using @Mock

The code below creates a mock object without using @Mock.

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

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

Create mock object with @Mock

You can create a mock object using @Mock like this: Using @Mock allows you to create mock objects with less code.

@Mock
List mockList;

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

The following is an example of stubbing with when.

@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

Here is how to create a spy object without @Spy.

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

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

Create a spy object with @Spy

You can create a spy object using @Spy like this:

@Spy
List spyList;

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

The following is an example of stubbing with when.

@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

Here is the code using ArgumentCaptor without @Captor.

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

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

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

Create ArgumentCaptor object with @Captor

You can create an ArgumentCaptor object using @Captor like this:

@Captor
ArgumentCaptor argCaptor;

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

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

@InjectMocks

Here are the two classes used in this example, Contacts and 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 object injection

Inside the Contacts class is a ContactsDatabase object. I want to test the Contacts object, but it is not easy to test because it has a dependency with ContactsDatabase. So, I want to make a Mock of ContactsDatabase to test the functionality of Contacts.

First, make the ContactsDatabase a Mock object, as in the following code: And if you pass a Mock object as an argument of Contacts, Contacts is an actual object, but the ContactsDatabase object inside becomes a mock object.

By making the Mock object return the desired value as when, you can test how Contacts behave in specific situations.

@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));
}

Injection of Mock objects with @InjectMocks

The above example created a constructor called ‘Contacts(ContactsDatabase db)’ to inject a Mock object into Contacts.

@InjectMocks allows you to inject Mock objects without creating a constructor.

First, delete the Contacts(ContactsDatabase db) constructor as follows.

public class Contacts {
    private ContactsDatabase db;

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

If you define Contacts as a Member variable with @InjectMocks as follows, objects created with @Mock are injected into Contacts.

@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));
}

If you run it, you can see that it works the same as the example above.

Injection of Mock object into Spy object

You cannot use @InjectMocks to inject mocks into Spy objects. When creating an object, you must use a method of passing a mock to the constructor as an argument.

You can create a spy object by passing the Contacts object injected with the Mock object of ContactsDatabase as an argument to spy() as follows.

@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));
}

Clean up

We learned how to use @Mock, @Spy, @Captor, and @InjectMocks. This annotation allows you to write test code with less code.

codechachaCopyright ©2019 codechacha