こんにちは。itouです。
今回は、外部サーバへHTTPリクエストする機能をJUnitでテストをする際
実際に外部サーバを建てることなくテストできる方法を記載いたします。
検証環境
- Eclipse 4.4.1
- Java 1.7
- JUnit 4.12
- JMockit 1.17
- httpclient 4.4.1
Eclipse動作確認手順
Eclipseプロジェクトを添付しておりますので
動作を確認しながらご覧いただければ幸いです。
opentone_labs_201506.zip
- 「ファイル」-「インポート」-「既存プロジェクトをワークスペースへ」を選択
- 「アーカイブ・ファイルの選択」でopentone_labs_201506.zipを選択しインポート
- プロジェクトを右クリックし、「実行」-「JUnit テスト」
- JUnitが全て緑(成功)していることを確認
テスト対象機能説明
今回は「引数のURLに対してGETでHTTPリクエストを行い、HTTPステータスを返却する」という機能をテストします。
・HttpServiceImpl.java(テスト対象機能)
[code] @Service public class HttpServiceImpl implements HttpService { @Override public int requestUrl(String url) throws IOException { CloseableHttpClient httpClient = null; CloseableHttpResponse response = null; int statusCode; try { // CloseableHttpClientを生成 httpClient = HttpClients.createDefault(); // GETでHTTPリクエストを作成 HttpGet request = new HttpGet(url); // HTTPリクエストを行い、HTTPレスポンスを取得 response = httpClient.execute(request); // HTTPレスポンスよりHTTPステータスを取得 statusCode = response.getStatusLine().getStatusCode(); } finally { if (httpClient != null) { // CloseableHttpClientをclose httpClient.close(); } if (response != null) { // CloseableHttpResponseをclose response.close(); } } // HTTPステータスを返却 return statusCode; } } [/code]
外部サーバがある状態でのJUnit
比較の為、まずは外部サーバがある状態でJUnitを実施してみます。
外部サーバにはexample.comを指定します。
ブラウザで https://example.com/ と指定すれば表示されるとおり、実存するサーバです。
・UnusedMockedHttpServiceTest.java
[code] @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/application-context.xml" }) public class UnusedMockedHttpServiceTest { @Autowired private HttpService httpService; /** * HTTPステータス=200 OK * * @throws Exception * Exception */ @Test public void example200() throws Exception { // http://example.com/ にHTTPリクエスト int actual = httpService.requestUrl("http://example.com/"); // HTTPステータスが200 OKであること assertThat(actual, is(HttpStatus.OK.value())); } /** * HTTPステータス= 404 Not Found * * @throws Exception * Exception */ @Test public void example404() throws Exception { // http://example.com/not_found にHTTPリクエスト int actual = httpService.requestUrl("http://example.com/not_found"); // HTTPステータスが404 Not Foundであること assertThat(actual, is(HttpStatus.NOT_FOUND.value())); } } [/code]
http://example.com/ は存在するURLなので
HTTPステータス 200:OKが返却されます
http://example.com/not_found は存在しないURLなので
HTTPステータス 404 Not Foundが返却されます
外部サーバがない状態でのJUnit
いよいよ、本題の
外部サーバを使用せずにテストをした場合の方法を記載いたします。
外部サーバを指定してリクエストを発行する代わりに、
どのような記述になっているか着目してみてください。
・UsedMockedHttpServiceTest.java
[code] /** * HTTPステータス=500 Internal Server Error * * @throws Exception * Exception */ @Test public void example500() throws Exception { // HTTPステータス500 Internal Server Errorが返却されるようモックに設定 mockHttpClientSet(HttpStatus.INTERNAL_SERVER_ERROR.value()); // モックを使用するので、実際には ★dummy★ にリクエストされない int actual = httpService.requestUrl("★dummy★"); // HTTPステータスが500 Internal Server Errorであること assertThat(actual, is(HttpStatus.INTERNAL_SERVER_ERROR.value())); } [/code]
requestUrlの引数に”★dummy★”と指定して、外部サーバを指定せずにリクエストを発行しましたが
HTTPステータス「500 Internal Server Error」が返却されてテストOKとなっています。
HTTPリクエスト前に実施している「mockHttpClientSet」にご注目ください。
後述する「モック」と呼ばれる仕組みを利用すると、返却するHTTPステータスをあらかじめ指定することができます。
これによって外部サーバに実際にアクセスしなくても、テストしたい振る舞いに合わせた
HTTPリクエストの結果を指定することができます。
モックの仕組み
今回は「JMockit」というモックフレームワークを使用しています。
「JMockit」の詳細は http://jmockit.org/ をご参考ください。
今回は、レスポンス値を指定するモックの機能に絞ってご説明いたします。
モックを定義しているスーパークラスのAbstractMockedTest.javaを確認します
CloseableHttpClientをモック化し
CloseableHttpClientのexecuteを使用する場合は
mockHttpClientSetで指定のHTTPステータスにより生成した、HttpResponseImplDummyが返却されるようにしています。
・AbstractMockedTest.java
[code] public abstract class AbstractMockedTest { @Mocked private CloseableHttpClient mockHttpClient; /** * CloseableHttpClient#execute のモック設定 * * @param httpStatus * HTTPステータス * @throws IOException * IOException */ protected void mockHttpClientSet(final int httpStatus) throws IOException { new NonStrictExpectations() { { // モックの対象メソッドを指定 mockHttpClient.execute((HttpUriRequest) any); // CloseableHttpClient#executeの戻り値を生成 result = new HttpResponseImplDummy(httpStatus); } }; } } [/code]
・HttpResponseImplDummy.java
[code] public class HttpResponseImplDummy implements CloseableHttpResponse { /** * ステータスライン */ private StatusLine statusLine; /** * HTTPレスポンスのうち、ステータスライン(HTTPステータス)を定義 * * @param httpStatus * HTTPステータス */ public HttpResponseImplDummy(int httpStatus) { setStatusLine(new StatusLineImpl(httpStatus)); } /** * ステータスラインを実装 * * @author opentone * */ public class StatusLineImpl implements StatusLine { private final ProtocolVersion protocolVersion; private final int statusCode; private final String reasonPhrase; private StatusLineImpl(int statusCode) { this.protocolVersion = new ProtocolVersion("HTTP", 1, 1); this.statusCode = statusCode; this.reasonPhrase = "OK"; } @Override public ProtocolVersion getProtocolVersion() { return protocolVersion; } @Override public int getStatusCode() { return statusCode; } @Override public String getReasonPhrase() { return reasonPhrase; } } @Override public StatusLine getStatusLine() { return this.statusLine; } @Override public void setStatusLine(StatusLine arg0) { this.statusLine = arg0; } : : 以下略 : : [/code]
まとめ
モックを利用することで外部サーバを建てずにHTTPリクエストのテストをする方法を紹介しました。
また今回はご紹介しませんでしたが、
モックの呼ばれた回数や、モックに渡されたパラメータの検証も可能です。
詳しくは、 http://jmockit.org/ をご参考ください。
HTTPリクエストのテストにおけるモック活用のご紹介でした。