模擬したいJavaクラスの1つにURLオブジェクトがありますが、これは最終クラスなのでできません。上のレベルに移動したくないので、InputStreamを模擬します。それでもテストされていないコードが残ります(厳格なテストカバレッジ基準があります)。
JMockItの反射力を試しましたが、Macで動作し、Javaエージェントハンドラーに問題があり、解決できませんでした。
では、junitテストで実際のURLを使用しないソリューションはありますか?
最終的な(またはC#で封印されている)ために簡単にモックできないクラスがある場合、通常のルートは、クラスの周りにラッパーを記述し、実際のクラスを使用する場所でラッパーを使用することです。次に、必要に応じてラッパークラスをモックアウトします。
Robが言ったように、URLから返された接続をモックしたい場合は、URLStreamHandler
を拡張できます。たとえば、mockitoの場合:
final URLConnection mockUrlCon = mock(URLConnection.class);
ByteArrayInputStream is = new ByteArrayInputStream(
"<myList></myList>".getBytes("UTF-8"));
doReturn(is).when(mockUrlCon).getInputStream();
//make getLastModified() return first 10, then 11
when(mockUrlCon.getLastModified()).thenReturn((Long)10L, (Long)11L);
URLStreamHandler stubUrlHandler = new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return mockUrlCon;
}
};
URL url = new URL("foo", "bar", 99, "/foobar", stubUrlHandler);
doReturn(url).when(mockClassloader).getResource("pseudo-xml-path");
私は次のように行きました:
public static URL getMockUrl(final String filename) throws IOException {
final File file = new File("testdata/" + filename);
assertTrue("Mock HTML File " + filename + " not found", file.exists());
final URLConnection mockConnection = Mockito.mock(URLConnection.class);
given(mockConnection.getInputStream()).willReturn(
new FileInputStream(file));
final URLStreamHandler handler = new URLStreamHandler() {
@Override
protected URLConnection openConnection(final URL arg0)
throws IOException {
return mockConnection;
}
};
final URL url = new URL("http://foo.bar", "foo.bar", 80, "", handler);
return url;
}
これにより、モックデータを含む実際のURLオブジェクトが得られます。
クラスパスからURLをロードできるURLHandlerを使用しました。だから次の
new URL("resource:///foo").openStream()
クラスパス内からfooという名前のファイルを開きます。これを行うには、 共通ユーティリティライブラリ を使用してハンドラーを登録します。このハンドラーを使用するには、次を呼び出す必要があります。
com.healthmarketscience.common.util.resource.Handler.init();
これでリソースURLが利用可能になりました。
ラッパーを作成したくない場合:
URLStreamHandlerFactoryを登録します
必要なメソッドを公開する
チェーンをあざける
abstract public class AbstractPublicStreamHandler extends URLStreamHandler {
@Override
public URLConnection openConnection(URL url) throws IOException {
return null;
}
}
public class UrlTest {
private URLStreamHandlerFactory urlStreamHandlerFactory;
@Before
public void setUp() throws Exception {
urlStreamHandlerFactory = Mockito.mock(URLStreamHandlerFactory.class);
URL.setURLStreamHandlerFactory(urlStreamHandlerFactory);
}
@Test
public void should_return_mocked_url() throws Exception {
// GIVEN
AbstractPublicStreamHandler publicStreamHandler = Mockito.mock(AbstractPublicStreamHandler.class);
Mockito.doReturn(publicStreamHandler).when(urlStreamHandlerFactory).createURLStreamHandler(Matchers.eq("http"));
URLConnection mockedConnection = Mockito.mock(URLConnection.class);
Mockito.doReturn(mockedConnection).when(publicStreamHandler).openConnection(Matchers.any(URL.class));
Mockito.doReturn(new ByteArrayInputStream("hello".getBytes("UTF-8"))).when(mockedConnection).getInputStream();
// WHEN
URLConnection connection = new URL("http://localhost/").openConnection();
// THEN
Assertions.assertThat(new MockUtil().isMock(connection)).isTrue();
Assertions.assertThat(IOUtils.toString(connection.getInputStream(), "UTF-8")).isEqualTo("hello");
}
}
PS:最後の行の後に番号付きリストの自動間隔をキャンセルする方法がわかりません
Powermockを使用してこれを行うことができると思います。最近、PowerMockを使用してURLクラスをモックすることができました。お役に立てれば。
/ *実際のクラス* /
import Java.net.MalformedURLException;
import Java.net.URL;
public class TestClass {
public URL getUrl()
throws MalformedURLException {
URL url = new URL("http://localhost/");
return url;
}
}
/ *テストクラス* /
import Java.net.URL;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(value = { TestClass.class })
public class TestClassTest {
private TestClass testClass = new TestClass();
@Test
public void shouldReturnUrl()
throws Exception {
URL url = PowerMockito.mock(URL.class);
PowerMockito.whenNew(URL.class).withParameterTypes(String.class)
.withArguments(Mockito.anyString()).thenReturn(url);
URL url1 = testClass.getUrl();
Assert.assertNotNull(url1);
}
}
JMockitでは、Java.net.URLのような最終的なJREクラスをモックすることができます。
Sun以外のJDK1.6の実装で使用可能なjdkDir/lib /tools.jarのAttachAPIも機能しないようです。このようなものはまだ新しすぎたり、進んだりしているか、他のJDKベンダー(Apple、IBMとJ9 JDK、OracleとJRockit JDK)から必要な注目を集めていなかったと思います。
したがって、クラスパスにtools.jarを含めることで問題が発生した場合は、「-javaagent:jmockit.jar」JVM引数を使用してみてください。 AttachAPIを使用せずに起動時にJavaエージェントを直接ロードするようにJVMに指示します。これはApple JDK 1.5 /1.6で機能するはずです。
最終的なデータオブジェクトをモックする理由をもう一度見てみましょう欲しい。定義上、実際のコードでオブジェクトをサブクラス化することはなく、テスト対象のオブジェクトになることもないため、このコードをホワイトボックステストする必要はありません。適切な(実際の)URLオブジェクトを渡して、出力を確認するだけです。
モックオブジェクトは、適切な実オブジェクトを作成するのが難しい場合、または実オブジェクトのメソッドに時間がかかるか、ステートフルな外部リソース(データベースなど)に依存している場合に役立ちます。この場合、どちらも当てはまらないため、適切なリソースの場所を表す実際のURLオブジェクトを作成できない理由がわかりません。
テストクラス自体を指すURLオブジェクトを作成します。
final URL url =
new URL("file://" + getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
URLクラスはインターフェースを実装していますか?その場合、直接構築するのではなく、制御の反転または構成可能なファクトリを使用してインスタンス化できます。これにより、現在の最終インスタンスではなく、テスト実行時にテストインスタンスを注入/構築できます。