JUnitのテストを行う際、処理の一部をMock化してテストしたい場合がある。例えば、SpringBootを利用したプログラムで、データベースアクセスを行うメソッドを呼び出す場合等である。
今回は、JUnitのライブラリである「Mockito」を利用して処理の一部をMock化してみたので、そのサンプルプログラムを共有する。「Mockito」については以下を参照のこと。
https://qiita.com/yakumo3390/items/c34f1f1625c3beffaf9c
前提条件
下記記事の実装が完了していること。今回は、下記記事で作成したプログラムを利用したJUnitプログラムを作成する。
サンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。
なお、上図の赤枠のうち、「DemoServiceImplTest.java」「SexEnum.java」が今回新規で作成したプログラムとなる。
「build.gradle」は前提条件の記事と変更していない。「testImplementation ‘org.springframework.boot:spring-boot-starter-test’」という記述が含まれていることで、Mockitoライブラリも利用できる。
plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' compile files('lib/ojdbc6.jar') implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1' }
実際に外部ライブラリを確認すると、下図の赤枠のように、Mockitoライブラリが含まれている。
また、「DemoServiceImpl.java」も、前提条件の記事と変更していない。下記ソースでは、今回テスト対象とするdemoFormListメソッドに関する部分のみを記載している。
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BindingResult; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Service public class DemoServiceImpl implements DemoService{ /** * ユーザーデータテーブル(user_data)へアクセスするマッパー */ @Autowired private UserDataMapper mapper; /** * {@inheritDoc} */ @Override public List<DemoForm> demoFormList() { List<DemoForm> demoFormList = new ArrayList<>(); //ユーザーデータテーブル(user_data)から全データを取得する Collection<UserData> userDataList = mapper.findAll(); for (UserData userData : userDataList) { demoFormList.add(getDemoForm(userData)); } return demoFormList; } /** * DemoFormオブジェクトに引数のユーザーデータの各値を設定する * @param userData ユーザーデータ * @return DemoFormオブジェクト */ private DemoForm getDemoForm(UserData userData){ if(userData == null){ return null; } DemoForm demoForm = new DemoForm(); demoForm.setId(String.valueOf(userData.getId())); demoForm.setName(userData.getName()); demoForm.setBirthYear(String.valueOf(userData.getBirthY())); demoForm.setBirthMonth(String.valueOf(userData.getBirthM())); demoForm.setBirthDay(String.valueOf(userData.getBirthD())); demoForm.setSex(userData.getSex()); demoForm.setSex_value(userData.getSex_value()); return demoForm; } }
さらに、今回作成したJUnitのプログラム「DemoServiceImplTest.java」の内容は以下の通り。
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 java.time.LocalDate; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertArrayEquals; import static org.mockito.Mockito.when; public class DemoServiceImplTest { /** * テスト対象のクラス * (今回はSpring Bootを利用しないため、Serviceではなく * ServiceImplを対象クラスに指定している) */ @InjectMocks private DemoServiceImpl demoServiceImpl; /** * テスト対象のクラス内で呼ばれるクラスのMockオブジェクト */ @Mock private UserDataMapper mapper; /** * 前処理(各テストケースを実行する前に行われる処理) */ @Before public void init() { //@Mockアノテーションのモックオブジェクトを初期化 //これを実行しないと@Mockアノテーション、@InjectMocksを付与した //Mockオブジェクトが利用できない MockitoAnnotations.initMocks(this); //Mockの設定 //mapper.findAll()メソッドを実行した際の戻り値をここで設定 when(mapper.findAll()).thenReturn(makeUserDataList()); } /** * DemoServiceImplクラスのdemoFormListメソッドの確認 */ @Test public void testDemoFormList(){ //テスト対象クラスのメソッドを実行 List<DemoForm> demoFormList = demoServiceImpl.demoFormList(); //取得内容をコンソールに表示 System.out.println("*** demoServiceImpl.demoFormList()の実行結果 ***"); for(DemoForm demoForm : demoFormList){ System.out.println(demoForm.toString()); } System.out.println(); //取得結果の内容を確認 //assertArrayEqualsは、引数の配列同士が同一かどうかを判定する assertArrayEquals(demoFormList.toArray(), expectDemoFormList().toArray()); } /** * ユーザーデータリストを生成する * @return ユーザーデータリスト */ private List<UserData> makeUserDataList(){ List<UserData> userDataList = new ArrayList<>(); //ユーザー1を追加 userDataList.add(makeUserData(Long.valueOf(1), "テスト プリン" , LocalDate.of(2012, 1, 15), SexEnum.WOMAN)); //ユーザー2を追加 userDataList.add(makeUserData(Long.valueOf(2), "テスト プリン2" , LocalDate.of(2013, 3, 19), SexEnum.MAN)); return userDataList; } /** * ユーザーデータを生成する * @param id ID * @param name 名前 * @param birthDay 生年月日 * @param sexEnum 性別Enum * @return ユーザーデータ */ private UserData makeUserData(Long id, String name, LocalDate birthDay , SexEnum sexEnum){ UserData userData = new UserData(); if(id != null) { userData.setId(id); } userData.setName(name); if(birthDay != null){ userData.setBirthY(birthDay.getYear()); userData.setBirthM(birthDay.getMonthValue()); userData.setBirthD(birthDay.getDayOfMonth()); } if(sexEnum != null){ userData.setSex(sexEnum.getSex()); userData.setSex_value(sexEnum.getSex_value()); } return userData; } /** * demoServiceImpl.demoFormList()を実行した際の、想定となる戻り値を生成する * @return DemoFormリスト */ private List<DemoForm> expectDemoFormList(){ List<DemoForm> demoFormList = new ArrayList<>(); //ユーザー1を追加 demoFormList.add(makeDemoForm(Long.valueOf(1), "テスト プリン" , LocalDate.of(2012, 1, 15), SexEnum.WOMAN)); //ユーザー2を追加 demoFormList.add(makeDemoForm(Long.valueOf(2), "テスト プリン2" , LocalDate.of(2013, 3, 19), SexEnum.MAN)); return demoFormList; } /** * Demoフォームオブジェクトを生成する * @param id ID * @param name 名前 * @param birthDay 生年月日 * @param sexEnum 性別Enum * @return Demoフォームオブジェクト */ private DemoForm makeDemoForm(Long id, String name, LocalDate birthDay , SexEnum sexEnum){ DemoForm demoForm = new DemoForm(); if(id != null){ demoForm.setId(String.valueOf(id)); } demoForm.setName(name); if(birthDay != null){ demoForm.setBirthYear(String.valueOf(birthDay.getYear())); demoForm.setBirthMonth(String.valueOf(birthDay.getMonthValue())); demoForm.setBirthDay(String.valueOf(birthDay.getDayOfMonth())); } if(sexEnum != null){ demoForm.setSex(sexEnum.getSex()); demoForm.setSex_value(sexEnum.getSex_value()); } return demoForm; } }
今回はMockitoを利用するため、@Beforeアノテーションが付与されているinitメソッド内で「MockitoAnnotations.initMocks(this);」を実行している。ちなみに、「@Before」アノテーションを付与したメソッドは、テストメソッド実行前に必ず呼ばれるメソッドである。
また、テスト対象のクラス「DemoServiceImpl」のオブジェクトに「@InjectMocks」を付与し、テスト対象クラス内で呼ばれるクラス「UserDataMapper」のオブジェクトに「@Mock」を付与することで、それぞれをMock化している。
さらに、テスト対象のクラス「DemoServiceImpl」のメソッド「demoFormList」内で、「UserDataMapper」クラスの「findAll」メソッドが呼ばれているため、この結果をMock化するため、initメソッド内で「when(mapper.findAll()).thenReturn(makeUserDataList());」を実行している。これで、「UserDataMapper」クラスの「findAll」メソッドが呼ばれたときに、makeUserDataList()メソッドの戻り値が返却されるようになる。
また、上記JUnitのテストプログラムで利用しているEnumクラスは、以下の通り。
package com.example.demo; public enum SexEnum { MAN("1", "男") ,WOMAN("2", "女"); private String sex; private String sex_value; SexEnum(String sex, String sex_value){ this.sex = sex; this.sex_value = sex_value; } public String getSex(){ return this.sex; } public String getSex_value(){ return sex_value; } }
なお、Enumについては、下記記事にて記載している。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/junit-mockito-mock/demo
さらに、今回作成したJUnitプログラムを実行した結果は以下の通りで、コンソールに、Mock化した、「UserDataMapper」クラスの「findAll」メソッドの戻り値を利用した結果が返却されていることが確認できる。
要点まとめ
- Mockitoを利用すると、処理の一部をMock化してJUnitによるテストが行える。
- Mockitoを利用したMockオブジェクトが利用できるためには、「MockitoAnnotations.initMocks(this);」を実行する必要がある。
- テスト対象のクラスのオブジェクトに「@InjectMocks」を付与し、テスト対象クラス内で呼ばれるクラスのオブジェクトに「@Mock」を付与することで、Mock化が行える。
- 「when(実行メソッド).thenReturn(戻り値);」と記述することで、実行メソッドの戻り値を設定できる。