JUnit

JUnitのMockitoの@Spyを利用してMock化するメソッドを限定してみた

これまではMockオブジェクトを利用し、テスト対象クラスから呼ばれるクラスのメソッドをMock化してきたが、@Spyアノテーションを利用すると、テスト対象クラスから呼ばれるクラスの一部メソッドのみをMock化できる。

今回は@Spyアノテーションを利用したサンプルプログラムを作成してみたので、共有する。

前提条件

下記記事の「起動ポートの変更」までの手順が完了していること。

IntelliJ IDEA上でGradleを使ってWeb画面のSpring Bootプロジェクトを作成してみたSpring Bootのプロジェクトを新規作成を「IntelliJ IDEA」のメニューから実施しようとしたところ、無料の「Commun...

やってみたこと

  1. テスト対象プログラムの作成と実行
  2. JUnitのプログラムの作成と実行

 

テスト対象プログラムの作成と実行

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上図の赤枠は、今回記載するサンプルプログラムの内容である。

テスト対象のコントローラクラスの内容は以下の通り。

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class DemoController {

    @Autowired
    private DemoComponent demoComponent;

    @RequestMapping("/")
    public String index(Model model){
        String realString1 = demoComponent.getRealString1();
        String realString2 = demoComponent.getRealString2();
        demoComponent.testVoid();

        model.addAttribute("realString1", realString1);
        model.addAttribute("realString2", realString2);
        return "index";
    }
}



また、上記プログラムから呼び出されるコンポーネントクラスの内容は以下の通り。

package com.example.demo;

import org.springframework.stereotype.Component;

@Component
public class DemoComponent {

    public String getRealString1(){
        return "realString1";
    }

    public String getRealString2(){
        return "realString2";
    }

    public void testVoid(){
        System.out.println("testVoid");
    }
}



さらに、画面のHTMLファイルの内容は以下の通り。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index page</title>
</head>
<body>
<p th:text="${realString1}">ここにrealString1の値が設定されます</p>
<p th:text="${realString2}">ここにrealString2の値が設定されます</p>
</body>
</html>

また、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスすると、以下の画面が表示される。
プログラムの実行結果

なお、上記はDI(Dependency Injection)を利用している。DIについては、下記サイトを参照のこと。

Spring BootでDIを利用してみたSpringフレームワークの基本として、DI(Dependency Injection)という概念がある。 DIは日本語で「依存性...

JUnitのプログラムの作成と実行

テスト対象のコントローラクラス「DemoController.java」から呼ばれるコンポーネントクラス「DemoComponent.java」を、@Spyアノテーションを利用して一部をMock化してみたJUnitのサンプルプログラムの内容は以下の通り。

package com.example.demo;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.springframework.ui.Model;

import java.util.Map;

public class DemoControllerTest1 {

    /**
     * テスト対象クラス
     */
    @InjectMocks
    private DemoController demoController;

    /**
     * テスト対象のクラス内で呼ばれるクラスを@Spyで設定
     * Spyアノテーションを付与すると、Mock化するメソッドを
     * 対象クラスの一部に限定できる
     */
    @Spy
    private DemoComponent demoComponent;

    /**
     * 前処理(各テストケースを実行する前に行われる処理)
     */
    @Before
    public void init() {
        //@Mockアノテーションのモックオブジェクトを初期化
        //これを実行しないと@Mockアノテーション、@InjectMocksを付与した
        //Mockオブジェクトが利用できない
        MockitoAnnotations.initMocks(this);

        //Mockオブジェクト呼出時の値を設定
        //demoComponent.getRealString1が呼ばれた場合のみMock設定し、
        //demoComponent.getRealString2が呼ばれた場合はMock設定しない
        doReturn("mockString1").when(demoComponent).getRealString1();

        //voidメソッドが呼ばれた場合は何もしないようにするには、下記のMock設定が必要
        doNothing().when(demoComponent).testVoid();
    }

    /**
     * DemoControllerクラスのindexメソッドの確認
     */
    @Test
    public void testDemoController(){
        //modelオブジェクトを取得し、テスト対象クラスのメソッドを実行
        Model model = DemoControllerTestUtil.getModel();
        String returnVal = demoController.index(model);

        //戻り値が"index"であることを確認
        assertEquals("index", returnVal);

        //modelオブジェクトの設定値を確認
        //demoComponent.getRealString1が呼ばれた場合は設定したMockの戻り値が設定され、
        //demoComponent.getRealString2が呼ばれた場合は実際の戻り値が設定される
        Map<String, Object> modelValue = model.asMap();
        assertEquals("mockString1", modelValue.get("realString1"));
        assertEquals("realString2", modelValue.get("realString2"));

        //modelオブジェクトの設定値を出力
        System.out.println("realString1の値 : " + modelValue.get("realString1")
                        + ", realString2の値 : " + modelValue.get("realString2"));
    }
}

上記プログラムによって、demoComponent.getRealString1()を呼び出した際はMock化した値「mockString1」が取得でき、demoComponent.getRealString2()を呼び出した際は実際の値「realString2」が取得できる。また、void型のdemoComponent.testVoid()を呼び出した際は何もしない設定となる。



また、上記プログラムから呼ばれる、Modelオブジェクトを生成するプログラムである「DemoControllerTestUtil.java」の内容は以下の通り。

package com.example.demo;

import org.springframework.ui.Model;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class DemoControllerTestUtil {

    public static Model getModel(){
        return new Model() {
            private Map<String, Object> modelMap = new HashMap<>();
            @Override
            public Model addAttribute(String attributeName, Object attributeValue) {
                modelMap.put(attributeName, attributeValue);
                return null;
            }

            @Override
            public Model addAttribute(Object attributeValue) {
                return null;
            }

            @Override
            public Model addAllAttributes(Collection<?> attributeValues) {
                return null;
            }

            @Override
            public Model addAllAttributes(Map<String, ?> attributes) {
                return null;
            }

            @Override
            public Model mergeAttributes(Map<String, ?> attributes) {
                return null;
            }

            @Override
            public boolean containsAttribute(String attributeName) {
                return false;
            }

            @Override
            public Map<String, Object> asMap() {
                return modelMap;
            }
        };
    }
}

上記プログラムを実行した結果は以下の通り。
コントローラテストクラス実行結果1



同等の処理を@Mockアノテーションを利用してMock化してみたサンプルプログラムの内容は以下の通り。

package com.example.demo;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.ui.Model;

import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;

public class DemoControllerTest2 {

    /**
     * テスト対象クラス
     */
    @InjectMocks
    private DemoController demoController;

    /**
     * テスト対象のクラス内で呼ばれるクラスを@Mockで設定
     */
    @Mock
    private DemoComponent demoComponent;

    /**
     * 前処理(各テストケースを実行する前に行われる処理)
     */
    @Before
    public void init() {
        //@Mockアノテーションのモックオブジェクトを初期化
        //これを実行しないと@Mockアノテーション、@InjectMocksを付与した
        //Mockオブジェクトが利用できない
        MockitoAnnotations.initMocks(this);

        //Mockオブジェクト呼出時の値を設定
        //demoComponent.getRealString1が呼ばれた場合のみMock設定し、
        //demoComponent.getRealString2が呼ばれた場合はMock設定しない
        doReturn("mockString1").when(demoComponent).getRealString1();
    }

    /**
     * DemoControllerクラスのindexメソッドの確認
     */
    @Test
    public void testDemoController(){
        //modelオブジェクトを取得し、テスト対象クラスのメソッドを実行
        Model model = DemoControllerTestUtil.getModel();
        String returnVal = demoController.index(model);

        //戻り値が"index"であることを確認
        assertEquals("index", returnVal);

        //modelオブジェクトの設定値を確認
        //demoComponent.getRealString1が呼ばれた場合は設定したMockの戻り値が設定され、
        //demoComponent.getRealString2が呼ばれた場合はnullが設定される
        Map<String, Object> modelValue = model.asMap();
        assertEquals("mockString1", modelValue.get("realString1"));
        assertNull(modelValue.get("realString2"));

        //modelオブジェクトの設定値を出力
        System.out.println("realString1の値 : " + modelValue.get("realString1")
                + ", realString2の値 : " + modelValue.get("realString2"));
    }
}

上記プログラムによって、demoComponent.getRealString1()を呼び出した際はMock化した値「mockString1」が取得でき、demoComponent.getRealString2()を呼び出した際はnullが取得される。また、void型のdemoComponent.testVoid()を呼び出した際の設定は特に記載していないが、何もしない設定となる。

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/junit-mockito-spy/demo

上記プログラムを実行した結果は以下の通り。
コントローラテストクラス実行結果2

要点まとめ

  • @Spyアノテーションを利用すると、テスト対象クラスから呼ばれるクラスの一部のみをMock化することができる。
  • @Spyアノテーションを付与したクラスのvoid型メソッドをMock化するには、MockitoライブラリのdoNothing()メソッドを利用する。