これまではMockオブジェクトを利用し、テスト対象クラスから呼ばれるクラスのメソッドをMock化してきたが、@Spyアノテーションを利用すると、テスト対象クラスから呼ばれるクラスの一部メソッドのみをMock化できる。
今回は@Spyアノテーションを利用したサンプルプログラムを作成してみたので、共有する。
前提条件
下記記事の「起動ポートの変更」までの手順が完了していること。
やってみたこと
テスト対象プログラムの作成と実行
作成したサンプルプログラムの構成は以下の通り。
なお、上図の赤枠は、今回記載するサンプルプログラムの内容である。
テスト対象のコントローラクラスの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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"; } } |
また、上記プログラムから呼び出されるコンポーネントクラスの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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ファイルの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 | <!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については、下記サイトを参照のこと。
JUnitのプログラムの作成と実行
テスト対象のコントローラクラス「DemoController.java」から呼ばれるコンポーネントクラス「DemoComponent.java」を、@Spyアノテーションを利用して一部をMock化してみたJUnitのサンプルプログラムの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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」の内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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; } }; } } |
同等の処理を@Mockアノテーションを利用してMock化してみたサンプルプログラムの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 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
要点まとめ
- @Spyアノテーションを利用すると、テスト対象クラスから呼ばれるクラスの一部のみをMock化することができる。
- @Spyアノテーションを付与したクラスのvoid型メソッドをMock化するには、MockitoライブラリのdoNothing()メソッドを利用する。