【问题标题】:Mocking a URL in Java在 Java 中模拟 URL
【发布时间】:2009-02-19 14:26:00
【问题描述】:

在我们想要模拟的 Java 类之一中有一个 URL 对象,但它是最终类,所以我们不能。我们不想更上一层楼,并模拟 InputStream,因为这仍然会给我们留下未经测试的代码(我们有严格的测试覆盖标准)。

我尝试了 jMockIt 的反射功能,但我们在 Mac 上工作,Java 代理处理程序存在我无法解决的问题。

那么有没有在junit测试中不涉及使用真实URL的解决方案呢?

【问题讨论】:

  • (java.net.URI 优先于 URL。)
  • Tom Hawtin:同意,我被标记为假设 URL 实例是一些自定义实现。下次请更明确 - java.net.URL 很容易编写。

标签: java url mocking


【解决方案1】:

就像 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");

【讨论】:

    【解决方案2】:

    当我有一个类因为它是最终的(或在 C# 中密封)而不能轻易模拟时,我通常的做法是在该类周围编写一个包装器,并在我使用实际类的任何地方使用该包装器。然后我会根据需要模拟包装类。

    【讨论】:

    • 是的,但是我需要对包装器进行单元测试,其中包含 URL 类,问题仍然存在。
    • 没有。包装器非常薄,只反映了 URL 上的方法。你不应该对它进行单元测试,你应该能够通过检查来验证它。
    • 嗯,是的,这是真的。正如我上面所说,我们有非常严格的覆盖标准,我需要将包装从我们的覆盖范围中排除。但这是我们最初提出的解决方案,我想这是有道理的。谢谢!
    • “相当严格的覆盖率标准” - 代码覆盖率是一个负面指标,这意味着它只会在你有覆盖率时提供有用的信息。一旦你得到覆盖,它并没有真正告诉你代码的质量。即,具有覆盖率并不表示经过良好测试的代码。如果可以的话,我建议你尝试改变严格的标准。
    • 这被标记为已接受的答案,是否有一个示例说明您如何编写包装器并测试包装器而不是new URL
    【解决方案3】:

    我选择了以下内容:

    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 对象,其中包含我的模拟数据。

    【讨论】:

    • 好答案!在尝试使用 Powermock 15 分钟后,我也这样做了
    • 这是唯一对我有用的解决方案!
    【解决方案4】:

    如果您不想创建包装器:

    注册一个 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 : 我不知道如何取消最后一行后的编号列表自动间距

    【讨论】:

    【解决方案5】:

    我认为您可以使用 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);
        }
    }
    

    【讨论】:

    • 彼得你能帮我吗? link
    • 正如你提到的,使用 PowerMockito.mock 而不是 Mockito.mock 很重要
    【解决方案6】:

    我使用了一个 URLHandler,它允许我从类路径加载一个 URL。所以以下

    new URL("resource:///foo").openStream()
    

    将从类路径中打开一个名为 foo 的文件。为此,我使用common utility library 并注册一个处理程序。要使用此处理程序,您只需调用:

    com.healthmarketscience.common.util.resource.Handler.init();
    

    资源 URL 现在可用。

    【讨论】:

    • IMO,最好使用 URL 构造函数,而不是在 URL 类中弄乱静态。
    【解决方案7】:

    我会再看看你为什么想要模拟一个最终的数据对象。由于根据定义,您不会在实际代码中对对象进行子类化,并且它不会成为被测对象,因此您不需要对此代码进行白盒测试;只需传入任何合适的(真实)URL 对象,然后检查输出。

    当难以创建合适的真实对象,或者真实对象的方法要么很耗时,要么依赖于一些有状态的外部资源(如数据库)时,模拟对象非常有用。这些都不适用于这种情况,所以我不明白为什么你不能只构造一个代表适当资源位置的真实 URL 对象。

    【讨论】:

    • 当然,我们希望将测试与任何类型的真实 url 分离,当我们调用 openConnection 时,我们会打开一个新的依赖项。
    • 我认为这是一个合法的问题。模拟框架不应该躲避这些事情......
    【解决方案8】:

    JMockit 确实允许您模拟最终的 JRE 类,例如 java.net.URL。

    似乎 jdkDir/lib/tools.jar 中的 Attach API 在 JDK 1.6 的实现中可用,而不是 Sun 的也不能正常工作。我猜这些东西仍然太新/太先进,或者根本没有得到其他 JDK 供应商(Apple、IBM 的 J9 JDK、Oracle 的 JRockit JDK)的必要关注。

    因此,如果您在类路径中包含 tools.jar 时遇到问题,请尝试使用“-javaagent:jmockit.jar”JVM 参数。它告诉 JVM 在启动时直接加载 java 代理,而不使用 Attach API。这应该适用于 Apple JDK 1.5/1.6。

    【讨论】:

      【解决方案9】:

      创建一个指向测试类本身的 URL 对象。

      final URL url = 
          new URL("file://" + getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
      

      【讨论】:

        【解决方案10】:

        URL 类是否实现了接口?如果是这样,那么您可以使用控制反转或可配置工厂来实例化它,而不是通过直接构造,这将允许您在测试运行时注入/构造一个测试实例,而不是您当前拥有的最终实例。

        【讨论】:

        • 不,正如我上面所说的,URL 是最终类,它来自 JDK。
        • 好的,我把这些问题读作“我们有一个由某个最终类实现的 URL 对象(比如 MyURL,据我所知,它可能有一个接口),而不是我们有一个 java 的实例。 net.URL 类本身。我认为问题可能更明确一些。
        • 当然它可以更明确,它是否是一个自定义 URL 有点模棱两可,道歉 - 但它仍然说它是最终的所以......
        • 某些东西可以是最终的并且仍然实现一个接口,适合工厂或注入构造。
        • 我正在 +1 回复 - 它不应该是 -1 因为你可以创建一个工厂来做你的包装类完全相同的事情(在接受的答案)确实如此。功能上相当,如果我必须进行第一次尝试,我可能会做什么。
        【解决方案11】:

        你可以这样模拟构造函数:

            new MockUp<URL>() {
                @Mock
                public void $init(Invocation invocation, String string) {
                }
            };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-09-06
          • 2017-01-08
          • 1970-01-01
          • 1970-01-01
          • 2011-10-29
          • 2010-09-08
          • 2015-04-01
          相关资源
          最近更新 更多