Mocking a URL in Java

前端 未结 10 1685
臣服心动
臣服心动 2020-12-14 08:16

We have a URL object in one of our Java classes that we want to mock, but it\'s a final class so we cannot. We do not want to go a level above, and mock the InputStream beca

相关标签:
10条回答
  • 2020-12-14 08:27

    Create a URL-object pointing to the test class itself.

    final URL url = 
        new URL("file://" + getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
    
    0 讨论(0)
  • 2020-12-14 08:30

    Like Rob said, if what you want is to mock the connection returned from the URL, you can extend URLStreamHandler. For instance, with 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");
    
    0 讨论(0)
  • 2020-12-14 08:31

    I would look again at why you want to mock a final data object. Since by definition you aren't subclassing the object in your actual code, and it's not going to be the object under test, you shouldn't need to white-box test this code; just pass in whatever (real) URL objects are appropriate, and check the output.

    Mock objects are useful when it's difficult to create a real object appropriate, or the real object's method are either time-consuming or depend on some stateful external resource (like a database). Neither of these apply in this case so I can't see why you can't just construct a real URL object representing the appropriate resource location.

    0 讨论(0)
  • 2020-12-14 08:37

    Does the URL class implement an interface? If so then you could instantiate it using inversion of control or a configurable factory, rather than by direct construction, this would allow you to inject/construct a test instance at test runtime rather than the final instance you currently have.

    0 讨论(0)
  • 2020-12-14 08:40

    I think you can use Powermock to do this. I was able to mock URL class using PowerMock lately. Hope this helps.

    /* Actual class */

    import java.net.MalformedURLException;
    import java.net.URL;
    
    public class TestClass {
    
        public URL getUrl()
            throws MalformedURLException {
    
            URL url = new URL("http://localhost/");
            return url;
        }
    }
    

    /* Test class */

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-14 08:41

    If you don't want to create a wrapper :

    Register a URLStreamHandlerFactory

    Make the method you want public

    Mock the chain

    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 : I don't know how to cancel the numbered list auto-spacing after last line

    0 讨论(0)
提交回复
热议问题