Spring Boot でメール送信する Web アプリケーションを作る ( その17 )( モックツール JMockit を試す )
概要
Spring Boot でメール送信する Web アプリケーションを作る ( その16 )( 日本語のファイル名の添付ファイル付メールを送信する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
以前購入した「JUnit実践入門」にモックに関する記述があり、モック用ライブラリでモックを作成することで呼び出す先のクラスやメソッドが未実装の時にもテストが出来るという点に興味がわきました。使い方等を調べてみたところ、いろいろな人の Blog で JMockit というモック用ライブラリが最強と言われていたので、試してみたいと思います。
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (68件) を見る
少し前まではテストを書くのは面倒だなと思っていましたが、いろいろ調べてみると Java のテストライブラリは便利で使いやすいものが揃っており、テストを書いて動作確認をしないなんてあり得ないな、と最近強く感じています。
ソフトウェア一覧
参考にしたサイト
IntelliJ IDEAでjarファイルのクラスパスの順番を変更する
http://shinodogg.com/?p=4799最強モックツール JMockit その1
http://genesis-tdsg.blogspot.jp/2013/08/jmockit.html最強モックツール JMockit その2 インストール編
http://genesis-tdsg.blogspot.jp/2013/08/jmockit_25.html最強モックツール JMockit その3 Webリクエスト編
http://genesis-tdsg.blogspot.jp/2013/09/jmockitweb.html最強モックツール JMockit その4 内部newクラス
http://genesis-tdsg.blogspot.jp/2013/09/jmockitnew.html最強モックツール JMockit その5 privateメソッド
http://genesis-tdsg.blogspot.jp/2013/09/jmockitprivate.html最強モックツール JMockit その6 staticクラス
http://genesis-tdsg.blogspot.jp/2013/09/jmockitstatic.html最強モックツール JMockit その7 複数回呼び出し
http://genesis-tdsg.blogspot.jp/2013/09/jmockit.html最強モックツール JMockit その8 例外実行
http://genesis-tdsg.blogspot.jp/2013/09/jmockit_30.html最強モックツール JMockit その9 メソッド丸替え
http://genesis-tdsg.blogspot.jp/2013/10/jmockit.html最強モックツール JMockit その10 コンストラクタ差し替え
http://genesis-tdsg.blogspot.jp/2013/10/jmockit_21.html最強モックツール JMockit その11 Eclipseプラグイン
http://genesis-tdsg.blogspot.jp/2013/10/jmockiteclipse.html【追加】最強モックツール JMockit その12 カバレッジオプション
http://genesis-tdsg.blogspot.jp/2014/04/jmockit.html【追加】最強モックツール JMockit その13 DI(Spring)対応
http://genesis-tdsg.blogspot.jp/2014/05/jmockitdi.html次世代のモックフレームワークであるJMockitの基本的な使い方
http://d.hatena.ne.jp/ryoasai/20110107/1294427245JMockit使い方メモ
http://qiita.com/opengl-8080/items/a49d4dae9067413ccdd6jmockitを使ったシステム時刻のモックとJVMの制限
http://haws.haw.co.jp/tech/jmockit-date-mock-jvm-limitation/
手順
ブランチの作成
build.gradle の修正
JMockit のライブラリをダウンロードして利用可能な状態にします。
build.gradle を リンク先の内容 に変更します。
Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
Getting started with the JMockit toolkit に「When using JUnit 4.5+, verify that jmockit.jar (or the equivalent Maven dependency) appears before JUnit in the classpath. 」と書かれていますので、classpath 内での jmockit.jar の位置を JUnit より前になるようにします。
メイン画面のメニューから「File」-「Project Structure...」を選択します。
「Project Structure」ダイアログが表示されます。画面左側のリストから「Project Settings」-「Modules」を選択します。
画面右側で「Dependencies」タブを選択した後、jmockit のライブラリを junit の上に移動します。移動後「OK」ボタンをクリックしてダイアログを閉じます。
MailsendService クラスをモックにして MailsendController クラスのテストを実行する
まずはモッククラスを定義しない簡単なテストを作成して MailsendController クラス、MailsendService クラスのメソッドがどちらも呼び出されることを確認します。
src/test/java/ksbysample/webapp/email/web/mailsend の下に MailsendControllerTestByJMockit.java を新規作成します。作成後、リンク先のその1の内容 に変更します。
ソースに breakpoint を設定します。src/main/java/ksbysample/webapp/email/web/mailsend の下の MailsendController クラスの send メソッド内の以下の位置に breakpoint を設定します。
同じパッケージ内にある MailsendService クラスの saveAndSendEmail メソッド内の以下の位置に breakpoint を設定します。
テストを Debug 実行します。MailsendControllerTestByJMockit クラス内の「MailsendServiceがモックの場合のテスト」クラスのクラス名にカーソルを移動した後、コンテキストメニューを表示して「Debug」-「MailsendControllerTestByJMockit$MailsendServiceがモックの場合のテスト」メニューを選択します。
テストが実行され、設定した2箇所の breakpoint を通過しテストが成功することが確認できます。
次に MailsendService クラスをモックにします。
src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその2の内容 に変更します。
再度テストを Debug 実行します。MailsendControllerTestByJMockit クラス内の「MailsendServiceがモックの場合のテスト」クラスのクラス名にカーソルを移動した後、コンテキストメニューを表示して「Debug 'MailsendControllerTestByJMocki...'」メニューを選択します。
テストが実行されますが、今度は MailsendController クラスの breakpoint のみ通過し、MailsendService クラスの breakpoint は通過しませんでした。
ここまでの内容をまとめると以下のようになります。
- テストクラス内に @Mocked アノテーションを付加したフィールドを定義すれば、Spring が自動生成する対象のクラスでも自動でモックに入れ替わります。
- 今回のテストの場合、MailsendController クラスの mailsendService フィールドにモックのインスタンスをセットし直す処理は必要ありませんでした。いろいろ見た blog の記事では
Deencapsulation.setField(...)
でセットし直すと書かれていたのですが、JMockit の機能が強化されたのか不要になったようです ( JMockit のサイトのドキュメントではおそらく Instantiation and injection of tested classes に書かれていると思われるのですがよく分かりませんでした )。 - void のメソッドの場合 ( MailsendService::saveAndSendEmail ) には、モッククラスのメソッドを定義する必要もありませんでした。
モッククラスは定義したテストクラスだけで有効なのか?
1つのテストクラス内に2つネストクラスを用意し、片方はモッククラスを定義し、片方はモッククラスを定義しません。
src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその3の内容 に変更します。
テストを Debug 実行します。MailsendControllerTestByJMockit クラス内の「MailsendServiceがモックの場合のテスト」クラスのクラス名にカーソルを移動した後、コンテキストメニューを表示して「Debug 'MailsendControllerTestByJMocki...'」メニューを選択します。
テストは「実体の場合」→「モックの場合」の順で実行されました。
breakpoint の通過は以下の状況でした。
- 最初の「実体の場合」は、設定した2箇所の breakpoint を通過しました。
- 次の「モックの場合」は、MailsendController クラスの breakpoint のみ通過し、MailsendService クラスの breakpoint は通過しませんでした。
テストが「モックの場合」→「実体の場合」の順で実行されるように変更します。src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその4の内容 に変更します。
再度テストを Debug 実行します。テストは「モックの場合」→「実体の場合」の順で実行されました。
breakpoint の通過は以下の状況でした。
- 「実体の場合」も「モックの場合」も MailsendController クラスの breakpoint のみ通過し、MailsendService クラスの breakpoint は通過しませんでした。
まとめると以下のようになります。
- モッククラスが定義されたテストクラスが実行された時に DI されるクラスはモッククラスに入れ替わります。
- 入れ替わりはモッククラスが定義されたテストクラスのテストメソッド終了後も続きます。元に戻りませんでした。
src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその2の内容 に戻します。
設定した breakpoint を全て解除します。
中で呼び出されるクラスの private メソッドをモックメソッドに入れ替える
MAIL001MailHelper クラスの private メソッド generateTextUsingVelocity だけをモックメソッドに変更してみます。
src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその5の内容 に変更します。
テストを実行します。MailsendControllerTestByJMockit クラス内の「MailsendServiceがモックの場合のテスト」クラスのクラス名にカーソルを移動した後、コンテキストメニューを表示して「Run 'MailsendControllerTestByJMocki...' with Coverage」メニューを選択します。
モックメソッドにより送信されるメールの本文が "これはテストです。" に変更され、テストが成功します。
モックメソッドでメソッド内の処理を変更する
1つ前では MAIL001MailHelper クラスの private メソッド generateTextUsingVelocity のモックメソッドを定義し、result に文字列をセットすることで戻り値を変更しましたが、今度はモックメソッドの処理を実装してみます。
src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその6の内容 に変更します。
テストを実行します。モックメソッドにより送信されるメールの本文が "あ,い" に変更され、テストが成功します。
JMockit でシステム日付を変更して指定した日付のメールを送信する
System.currentTimeMillis() のモックメソッドを作成して日付を変更してメールを送信するテストを作成してみます。
src/test/java/ksbysample/webapp/email/test の下に MockSystem.java を新規作成します。作成後、リンク先の内容 に変更します。
src/test/java/ksbysample/webapp/email/web/mailsend の下の MailsendControllerTestByJMockit.java を リンク先のその7の内容 に変更します。
テストを実行します。MailsendControllerTestByJMockit クラス内の「MailsendServiceがモックの場合のテスト」クラスのクラス名にカーソルを移動した後、コンテキストメニューを表示して「Run 'MailsendControllerTestByJMocki...' with Coverage」メニューを選択します。
テストは成功し、「最小値で送信ボタンをクリックした場合」で変更した日付は「最小値で送信ボタンをクリックした場合2」のテストの時には元に戻っていました。
反映の取り消し、1.0.x-jmockit ブランチの削除
- 今回の変更は反映しません。ブランチを 1.0.x に戻し、
git reset --hard
コマンドで反映を取り消して 1.0.x-jmockit ブランチを削除します。
感想
- いろいろな blog に書かれているとおり、最強と呼ばれるのにふさわしく確かに何でもできそうです。
- 1点だけ気になったのは、どこかでモッククラスを定義したままテストクラスを commit&push してしまうと、テストは通っているのに途中から実はモックでした、という問題が起きそうな気がしました。テストは必ずモックで行うというクラスやメソッドでなければ、モックは手元で開発している時だけ使用しバージョン管理システムには commit&push しない等のルールが必要かもしれません。
次回は。。。
- Spring Boot の 1.2.4 がリリースされていましたので、バージョンアップします。
- その後で送信済メール検索機能を実装します。
ソースコード
build.gradle
dependencies { def jdbcDriver = "org.postgresql:postgresql:9.4-1201-jdbc41" // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照 compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("org.springframework.boot:spring-boot-starter-velocity") compile("org.springframework.boot:spring-boot-starter-mail") compile("org.codehaus.janino:janino") testCompile("org.springframework.boot:spring-boot-starter-test") testCompile("org.yaml:snakeyaml") // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの compile("${jdbcDriver}") compile("org.seasar.doma:doma:2.2.0") compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16") compile("org.apache.commons:commons-lang3:3.4") compile("org.projectlombok:lombok:1.16.2") compile("com.google.guava:guava:18.0") testCompile("org.dbunit:dbunit:2.5.0") testCompile("org.subethamail:subethasmtp:3.1.7") testCompile("com.icegreen:greenmail:1.4.1") testCompile("org.jmockit:jmockit:1.17") // for Doma-Gen domaGenRuntime("org.seasar.doma:doma-gen:2.2.0") domaGenRuntime("${jdbcDriver}") }
- dependencies の中に
testCompile("org.jmockit:jmockit:1.17")
を追加します。
MailsendControllerTestByJMockit.java
■その1
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.test.MailServerResource; import ksbysample.webapp.email.test.MockMvcResource; import ksbysample.webapp.email.test.TestDataResource; import ksbysample.webapp.email.test.TestHelper; import mockit.Deencapsulation; import mockit.Mocked; import mockit.NonStrictExpectations; import mockit.integration.junit4.JMockit; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import javax.mail.MessagingException; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); } } }
■その2
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.test.MailServerResource; import ksbysample.webapp.email.test.MockMvcResource; import ksbysample.webapp.email.test.TestDataResource; import ksbysample.webapp.email.test.TestHelper; import mockit.Deencapsulation; import mockit.Mocked; import mockit.NonStrictExpectations; import mockit.integration.junit4.JMockit; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import javax.mail.MessagingException; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Mocked private MailsendService mailsendService; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); } } }
private MailsendService mailsendService;
を追加します。
■その3
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.test.MailServerResource; import ksbysample.webapp.email.test.MockMvcResource; import ksbysample.webapp.email.test.TestDataResource; import ksbysample.webapp.email.test.TestHelper; import mockit.Mocked; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.core.annotation.Order; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Mocked private MailsendService mailsendService; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceが実体の場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); } } }
- 「MailsendServiceが実体の場合のテスト」ネストクラスを追加します。こちらにはモッククラスは定義しません。
■その4
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.test.MailServerResource; import ksbysample.webapp.email.test.MockMvcResource; import ksbysample.webapp.email.test.TestDataResource; import ksbysample.webapp.email.test.TestHelper; import mockit.Mocked; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.core.annotation.Order; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceが実体の場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Mocked private MailsendService mailsendService; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); } } }
- 「MailsendServiceが実体の場合のテスト」ネストクラスを「MailsendServiceがモックの場合のテスト」ネストクラスの上に移動します。
■その5
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.helper.mail.MAIL001MailHelper; import ksbysample.webapp.email.test.MailServerResource; import ksbysample.webapp.email.test.MockMvcResource; import ksbysample.webapp.email.test.TestDataResource; import ksbysample.webapp.email.test.TestHelper; import mockit.Mocked; import mockit.NonStrictExpectations; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import javax.mail.internet.MimeMessage; import static mockit.Deencapsulation.invoke; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Mocked("generateTextUsingVelocity") private MAIL001MailHelper mockMAILl001MailHelper; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { new NonStrictExpectations() {{ invoke(mockMAILl001MailHelper, "generateTextUsingVelocity", withAny(MailsendForm.class)); result = "これはテストです。"; }}; mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); assertThat(mailServer.getMessagesCount(), is(1)); MimeMessage receiveMessage = mailServer.getFirstMessage(); assertThat(receiveMessage.getContent(), is("これはテストです。")); } } }
@Mocked private MailsendService mailsendService;
を削除します。@Mocked("generateTextUsingVelocity") private MAIL001MailHelper mockMAILl001MailHelper;
を追加します。モックにするメソッド名を @Mocked アノテーションに記述することで、そのメソッドだけがモックになり、他は実際のクラスの実装が使用されます。- 「最小値で送信ボタンをクリックした場合」テストメソッドの中に
new NonStrictExpectations() {{ ... }};
を追加し、mockit.Deencapsulation.invoke メソッドで generateTextUsingVelocity の戻り値を定義します。 - invoke メソッドにモックメソッドの引数を指定するのですが、Object 型の場合には any ではなく withAny(...) で指定しなければならない、という点が非常に分かりずらかったです。anyString 等の例はよく見かけれるのですが、withAny の例を見つけるまで結構時間がかかりました。
■その6
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.helper.mail.MAIL001MailHelper; import ksbysample.webapp.email.test.MailServerResource; import ksbysample.webapp.email.test.MockMvcResource; import ksbysample.webapp.email.test.TestDataResource; import ksbysample.webapp.email.test.TestHelper; import mockit.Delegate; import mockit.Mocked; import mockit.NonStrictExpectations; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import javax.mail.internet.MimeMessage; import static mockit.Deencapsulation.invoke; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Mocked("generateTextUsingVelocity") private MAIL001MailHelper mockMAILl001MailHelper; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { new NonStrictExpectations() {{ invoke(mockMAILl001MailHelper, "generateTextUsingVelocity", withAny(MailsendForm.class)); result = new Delegate<String>() { public String delegate(MailsendForm mailsendForm) { return mailsendForm.getName() + "," + mailsendForm.getNaiyo(); } }; }}; mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); assertThat(mailServer.getMessagesCount(), is(1)); MimeMessage receiveMessage = mailServer.getFirstMessage(); assertThat(receiveMessage.getContent(), is("あ,い")); } } }
最小値で送信ボタンをクリックした場合
テストメソッドのnew NonStrictExpectations() {{ ... }};
の中で、result = new Delegate<String>() { ... };
のように実装を変更します。Delegate の後の<...>
の中に指定するのは戻り値の型です。- テストメソッドの最後の assertThat を
assertThat(receiveMessage.getContent(), is("あ,い"));
に変更します。
■その7
package ksbysample.webapp.email.web.mailsend; import ksbysample.webapp.email.Application; import ksbysample.webapp.email.test.*; import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.yaml.snakeyaml.Yaml; import javax.mail.internet.MimeMessage; import java.util.Calendar; import java.util.Date; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class MailsendControllerTestByJMockit { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration @FixMethodOrder(MethodSorters.NAME_ASCENDING) public static class MailsendServiceがモックの場合のテスト { private final MailsendForm mailsendFormMin = (MailsendForm) new Yaml().load(getClass().getResourceAsStream("mailsendForm_min.yml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Rule @Autowired public MockMvcResource mvc; @Test public void 最小値で送信ボタンをクリックした場合() throws Exception { // 日付を 2015/01/01 にする MockSystem.setCurrentDate(2015, Calendar.JANUARY, 1, 0, 0, 0); mvc.nonauth.perform(TestHelper.postForm("/mailsend/send", this.mailsendFormMin)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/mailsend")) .andExpect(model().hasNoErrors()) .andExpect(model().errorCount(0)); assertThat(mailServer.getMessagesCount(), is(1)); MimeMessage receiveMessage = mailServer.getFirstMessage(); Calendar calendar = Calendar.getInstance(); calendar.set(2015, Calendar.JANUARY, 1, 0, 0, 0); System.out.println("★★★ " + receiveMessage.getSentDate()); assertThat(receiveMessage.getSentDate(), is(calendar.getTime())); } @Test public void 最小値で送信ボタンをクリックした場合2() { Date now = new Date(); System.out.println("■■■ " + now); } } }
- テストメソッドの実行順序をテストメソッド名順にしたいので、「MailsendServiceがモックの場合のテスト」テストクラスに
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
を付加します。 - 「最小値で送信ボタンをクリックした場合」テストメソッドの処理の最初に
MockSystem.setCurrentDate(2015, Calendar.JANUARY, 1, 0, 0, 0);
を追加します。 - 「最小値で送信ボタンをクリックした場合」テストメソッド内のメールのチェック内容を、
receiveMessage.getSentDate()
をチェックするものに変更します。 - 「最小値で送信ボタンをクリックした場合2」テストメソッドを追加します。「最小値で送信ボタンをクリックした場合」テストメソッドで変更した日付が、他のテストメソッドでも変更されたままなのかを確認するためのテストメソッドです。
MockSystem.java
package ksbysample.webapp.email.test; import mockit.Mock; import mockit.MockUp; import java.util.Calendar; public class MockSystem extends MockUp<System> { private static final MockSystem instance = new MockSystem(); private long mockDate; private MockSystem() { } public static void setCurrentDate(int year, int month, int date, int hourOfDay, int minute, int second) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month, date, hourOfDay, minute, second); instance.mockDate = calendar.getTime().getTime(); } @Mock public long currentTimeMillis() { return this.mockDate; } }
履歴
2015/06/08
初版発行。