【Mockit】モックメソッドの引数と呼び出し回数の検証

Mockitを使ってモック化したメソッドの呼び出し回数と呼び出し時に渡された引数の値を検証する方法についてまとめます。

準備

動作検証を行った環境は以下の通りです。

  • Java11
  • Spring Boot 2.1.11

テスト対象として以下のクラスを作成します。今回は商品の検索、保存を行うItemRepositoryクラスとItemRepositoryクラスを使用するItemServiceクラスを作成します。

ItemServiceクラスをテスト対象とし、依存先のItemRepositoryクラスをモック化します。

Itemクラス

package com.example.demo;

import lombok.Value;

@Value
public class Item {

    private int id;
    private String itemName;
    private int price;
}

ItemServiceクラス

リポジトリクラスを使用してItemの検索、保存を行うサービスクラスです。

package com.example.demo;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
@RequiredArgsConstructor
@Service
public class ItemService {

    private final ItemRepository itemRepository;

    // IDで検索
    public Item findById(int id) {

        return itemRepository.findById(id);
    }

    // 1つのItemを保存
    public void save(Item item) {

        var foundItem = findById(item.getId());
        if (foundItem == null) {
            itemRepository.save(item);
        }
    }

    // 複数のItemを保存
    public void save(List<Item> items) {

        items.forEach(this::save);
    }
}

ItemRepositoryクラス

リポジトリクラスです。未完成の状態を想定して各メソッドの処理は記述していません。

package com.example.demo;

import org.springframework.stereotype.Repository;

@Repository
public class ItemRepository {

    public Item findById(int id) {
        return null;
    }

    public void save(Item item) {

    }
}

テストコード

以下のようなテストコードを作成しました。このテストコードではItemRepositoryをモック化し、saveメソッドの実行回数と渡された引数を検証しています。

verifyメソッドを使用することでモック化したメソッドの呼び出し回数を検証することができます。実行回数はtimesメソッドを使って指定できます。

ArgumentCaptureクラスを使用することでモック化したメソッドに渡された引数の値を取得できます。取得した値をassertThatを使って検証しています。

package com.example.demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ItemServiceTest {

    @InjectMocks
    private ItemService itemService;

    @Mock
    private ItemRepository itemRepository;

    // ItemRepositoryのsaveメソッドが実行されないことをテスト
    @Test
    public void save_not_called_test() {

        var item = new Item(1, "newItem", 100);

        // findByIdメソッドをモック化
        when(itemRepository.findById(anyInt())).thenReturn(new Item(1, "MockItem", 100));
        itemService.save(item);

        // ItemRepositoryクラスのsaveメソッドは実行されない
        verify(itemRepository, times(0)).save(any());
    }

    // ItemRepositoryのsaveメソッドが1回実行されることをテスト
    // saveメソッドの引数を検証
    @Test
    public void save_called_test() {

        var item = new Item(1, "newItem", 100);

        when(itemRepository.findById(anyInt())).thenReturn(null);
        doNothing().when(itemRepository).save(any());

        itemService.save(item);

        var argumentCapture = ArgumentCaptor.forClass(Item.class);

        // ItemRepositoryクラスのsaveメソッドは1回実行される
        verify(itemRepository, times(1)).save(argumentCapture.capture());

        // 引数を検証
        var capturedItem = argumentCapture.getValue();
        assertThat(capturedItem.getId()).isEqualTo(1);
        assertThat(capturedItem.getItemName()).isEqualTo("newItem");
        assertThat(capturedItem.getPrice()).isEqualTo(100);
    }

    // ItemRepositoryのsaveメソッドが2回実行されることをテスト
    // saveメソッドの引数を検証
    @Test
    public void save_items_test() {

        var item1 = new Item(1, "newItem1", 100);
        var item2 = new Item(2, "newItem2", 200);
        var items = List.of(item1, item2);

        doNothing().when(itemRepository).save(any());

        itemService.save(items);

        var argumentCapture = ArgumentCaptor.forClass(Item.class);

        // ItemRepositoryクラスのsaveメソッドは2回実行される
        verify(itemRepository, times(2)).save(argumentCapture.capture());

        // 引数を検証
        var capturedItem2 = argumentCapture.getAllValues();
        assertThat(capturedItem2).extracting("id", "itemName", "price")
                .containsExactlyInAnyOrder(
                        tuple(1, "newItem1", 100),
                        tuple(2, "newItem2", 200)
                );
    }
}