久々にEasyMockやらJUnitやら使ってSpringのSimpleController部分のテストしていたが、
色々ど忘れ+はまったのでメモしておく。
(主としてど忘れが多い。)
ちなみにEasyMockに関する記述は省略していて、主としてSpringの設定周りが主。
この件に関しては、以前のエントリを参考のこと。
Controller部分Mock生成に関しては以下のソースが参考になった。
GoogleCode thick4
テスト自体は、ありがたいことにSpringのライブラリからMockオブジェクトが提供されているのでそれを流用する。
Servlet周りのMockオブジェクトはspring-mock.jarのorg.springframework.mock.webパッケージ中に各種そろっている。
- MockFilterConfig
- MockHttpServletRequest
- MockHttpServletResponse
- MockHttpSession
- MockPageContext
- etc…
但し、springのバージョンが古いせいか、一部のMockオブジェクトがないorz…。
ので、Spring2.0系のソースからちょろまかして、流用することにした。
流用したのは以下のMockオブジェクト。
- MockMultipartFile
- MockFileItem
- MockMultipartHttpServletRequest
- MockFilterChain
基本的にはコピペでOK。
所詮Mockオブジェクトなのでたいしたことはやっていないし。
但し、一部そのまま使えなかった以下の部分は修正した。
MockMultipartFile
/**
* 実装側が、org.springframework.web.multipart.commons.CommonsMultipartFile
* にキャストされて実装されていたため、暫定的にCommonsMultipartFileをimplementsするように実装
*/
public class MockMultipartFile extends CommonsMultipartFile {
// ... 省略
/**
* 直接FilterItemをコンストラクタで受け取って処理できるように拡張
*/
public MockMultipartFile(FileItem fileItem) {
this(fileItem.getFieldName(),
fileItem.getName(),
fileItem.getContentType(),
fileItem.get(),
fileItem);
}
// ... 省略
/**
* throws節が矛盾しているとエラーになるので、IOExceptionを除去
*/
@Override
public byte[] getBytes() /* throws IOException */ {
return this.content;
}
}
MockFileItem
存在していなかったので、自作しようと思ってだめもとで検索したら類似のやつを発見したので、
ありがたく参考にさせてもらう。感謝。
View Full Version : Spring 2 Breaks my File Upload Unit test
/**
* そのまま実行したら、一部エラーになったので修正してある。
*/
public class MockFileItem implements FileItem {
private String fieldName;
private String contentType;
private String name;
private String value;
private File writtenFile;
private boolean deleted;
public MockFileItem(String fieldName, String contentType, String name, String value) {
this.fieldName = fieldName;
this.contentType = contentType;
this.name = name;
this.value = value;
this.writtenFile = new File(this.value);
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.value.getBytes());
}
public String getContentType() {
return this.contentType;
}
public String getName() {
return this.name;
}
public boolean isInMemory() {
return true;
}
public long getSize() {
return this.value.length();
}
public byte[] get() {
// return value.getBytes();
try {
return FileUtils.readFileToByteArray(this.writtenFile);
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
return new byte[0];
}
}
public String getString(String encoding)
throws UnsupportedEncodingException {
return new String(get(), encoding);
}
public String getString() {
return this.value;
}
public void write(File file) throws Exception {
this.writtenFile = file;
}
public File getWrittenFile() {
return this.writtenFile;
}
public void delete() {
this.deleted = true;
}
public boolean isDeleted() {
return this.deleted;
}
public String getFieldName() {
return this.fieldName;
}
public void setFieldName(String s) {
this.fieldName = s;
}
public boolean isFormField() {
return (this.name == null);
}
public void setFormField(boolean b) {
throw new UnsupportedOperationException();
}
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
}
– MockMultipartHttpServletRequest
– MockFilterChain
これらはそのままコピーでたぶんOK。
以下テストケース実装。
テストクラスの起動タイミングでアプリケーション起動とみなし、@BeforeClassを使って、
ContextLoaderやServletContext、Bean定義ファイルの読み込みを行っている。
また、各メソッドレベル(@Test)の実行を一回のリクエストとして、
HttpServletRequest,HttpServletResponseを生成している。
/**
* テストクラス
*/
public class SampleControllerTest {
/** テスト対象Controller */
private static SampleController controller;
private static ApplicationContext applicationContext;
private static MockServletContext servletContext;
/**
* テストクラス前実行クラス
*/
@BeforeClass
public static void init() {
// Mock用のContextを生成
ContextLoader contextLoader = new ContextLoader();
servletContext = new MockServletContext();
String configLocationParam = StringUtils.arrayToDelimitedString(new String[]{
"/WEB-INF/spring/validator.xml",
"application-servlet.xml",
}, ",");
servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, configLocationParam);
ConfigurableWebApplicationContext context = (ConfigurableWebApplicationContext)
contextLoader.initWebApplicationContext(servletContext);
applicationContext = context;
controller = (SampleController) applicationContext.getBean("sampleController");
}
/**
* 実際のテストクラス
*/
@Test
public void ファイルアップロードテストSample {
try {
/** FileUploadで用いられるMultipartHttpServletRequestのMockを生成 */
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.setRequestURI("/sampe.html");
request.setMethod("POST");
MockFileItem fileItem = new MockFileItem(
"file",
"application/vnd.ms-excel;",
fileName,
"sample.xls");
request.addFile(new MockMultipartFile(fileItem));
MockHttpServletResponse response = new MockHttpServletResponse()
// リクエストの実行
ModelAndView mv = controller.handleRequest(request, response);
// 実行結果はresultとキーでMapに格納されているので取得
String mes = (String) mv.getModel().get("result");
assertEquals("OK", mes);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}
注意点
Springのautoproxyを有効にしている状態で、Bean定義ファイルの読み込みを行わせると、以下のようなエラーになる。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘hogehoge’ defined in ServletContext resource [/WEB-INF/application-servlet.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyAccessExceptionsException: PropertyAccessExceptionsException (1 errors); nested propertyAccessExceptions are: [org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy8] to required type [hoge.hoge.hoge] for property ‘piyo’]
PropertyAccessExceptionsException (1 errors)
org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy8] to required type [hoge.hoge.hoge] for property ‘piyo’
なんだか知らないがautoProxyを有効にしているとだめらしい。
参考:マキノ式ブログ
仕方ないのでAOPの設定部分の、定義ファイルを分離してテスト時には読み込まないように対応したが。
話はそれるが、テストケースのメソッドレベルで日本語が使えるのは非常にありがたいなと思う今日この頃である。