1

This unit test is failing.

package com.abc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.net.URI;
import java.net.URL;

import static org.junit.Assert.assertEquals;

@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@PrepareForTest({URI.class})
public class ITest2 {
    @Test
    public void test5() throws Exception {
        URI uri = new URI("http://www.google.com");

        final URL resourceUrl = ClassLoader.getSystemClassLoader().getResource("static/abc.png"); //EXISTS

        PowerMockito.whenNew(URL.class).withArguments(
                "http://www.google.com")
                .thenReturn(resourceUrl);

        URL url = uri.toURL(); // <--- At this point, url should be == resourceUrl

        assertEquals(resourceUrl, url); // <--- url is http://www.google.com and not ".../static/abc.png"
    }
}

This unit test is failing.

java.lang.AssertionError: 
Expected :file:/Users/hidden/target/classes/static/abc.png
Actual   :http://www.google.com
<Click to see difference>

Do you know why url != resourceUrl? What am I missing?

Here's the code of URI.toURL():

public URL toURL()
    throws MalformedURLException {
    if (!isAbsolute())
        throw new IllegalArgumentException("URI is not absolute");
    return new URL(toString());
}

Using Mockito 2.15 & Powermock 2.0.7.

Thank you.

Update:

Adding these don't help either. Just hacking away.

PowerMockito.whenNew(URL.class).withArguments(
        eq("http://www.google.com"))
        .thenReturn(resourceUrl);

PowerMockito.whenNew(URL.class).withArguments(Mockito.anyString()).thenReturn(resourceUrl);
PowerMockito.whenNew(URL.class).withArguments(any()).thenReturn(resourceUrl);
PowerMockito.whenNew("java.net.URL").withArguments(any()).thenReturn(resourceUrl);

PowerMockito.whenNew(URL.class).withParameterTypes(String.class)
        .withArguments("http://www.google.com")
        .thenReturn(resourceUrl);

PowerMockito.whenNew(URL.class).withAnyArguments().thenReturn(resourceUrl);

PowerMockito.whenNew(URL.class).withNoArguments().thenReturn(resourceUrl);
user674669
  • 10,681
  • 15
  • 72
  • 105
  • Does this answer your question? [Using PowerMockito.whenNew() is not getting mocked and original method is called](https://stackoverflow.com/questions/25317804/using-powermockito-whennew-is-not-getting-mocked-and-original-method-is-called) – kasptom Apr 15 '20 at 22:11
  • @kasptom, No it doesn't answer my question. Do you know what am I doing wrong? Thank you. – user674669 Apr 15 '20 at 22:20
  • PowerMockito has certain limitations when it comes to mocking system classes, check [here](https://github.com/powermock/powermock/wiki/Mock-System). However this approach is not possible for you as `java.net.URL` is yet another system class. – second Apr 17 '20 at 20:10
  • You could add a test which shows some more context of what you are really trying to do. Mabye mocking `java.net.URI` instead could solve your issue. That at least should be a possibility as long as the bytecode of the creating class can be modified. – second Apr 17 '20 at 20:30
  • @second, I have a simple test called test5() in my code sample above. – user674669 Apr 21 '20 at 12:06
  • @user674669: Yes, I have seen that part. But this is not something you need to test, it is an example of how you try to work with the framework. I image that this simple case does not directly translate to your real problem (class under test). However if your goal is to write tests for the `java.net.URL` class itself, the answer would be that you can not do that with `powermockito`. – second Apr 21 '20 at 16:39
  • @second, Can you provide a reference please. Look at https://metlos.wordpress.com/2012/09/14/the-dark-powers-of-powermock/ - this developer is successfully doing that. – user674669 Apr 21 '20 at 18:00
  • @user674669: You need to read the linked article more carefully. It explains why what you are trying to do here does not work and also explains how to work around it. The key is that in `attempt #3` instead of a system class some other class is (bytecode) modified, so that it can work. Also its mocking both `URI` and `URL`. – second Apr 22 '20 at 05:05
  • @second, I read that article and I am at attempt #3. I am intercepting the constructor of URL class which happens in URI class. So, I am preparing URI using PrepareForTest annotation. – user674669 Apr 22 '20 at 16:40
  • @user674669: I've added an answer and added a working example for the case the author is trying to do (using `junit` instead of `testng`). I hope that helps you. The devil is in the details, debuging it might help you understand how powermockito works. – second Apr 22 '20 at 20:33

1 Answers1

0

From the FAQ:

Question:

I cannot mock classes in from java.lang, java.net, java.io or other system classes, why?

Answer:

This is because they're loaded by Java's bootstrap classloader and cannot be byte-code manipulated by PowerMock's classloader. Since PowerMock 1.2.5 there's a work-around, please have a look at this simple example to see how it's done.

Note that the link to the example there is a dead one, it should be this one instead.


user674669 wrote:

I am intercepting the constructor of URL class which happens in URI class.

You are trying to do that. However this fails silently (and the real object is created instead).

user674669 wrote:

So, I am preparing URI using PrepareForTest annotation.

java.net.URI is a system class. As mentioned above PowerMockito is not able to manipulate the bytecode for it, so it won't have any effect. You will have to adjust the bytecode of a different class instead, which implies that you need to define a different class in the @PrepareForTest annotation.


A working example for the case mentioned from the linked article:

Tested with powermock 2.0.7, mockito 3.3.3, junit 4

public class ClassUnderTest {
    public InputStream method(boolean param, URI uri) throws Exception {
        String scheme = param ? "https" : "http";
        URI replacedUri = new URI(scheme, uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment());
        return replacedUri.toURL().openStream();
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassUnderTest.class)
public class MyTest { 

    @Test
    public void testMethod() throws Exception { 

        URI uri = new URI("blah://localhost");
        FileInputStream fis = new FileInputStream(new File(".", "existing.file"));
        
        URI uriMock = PowerMockito.mock(URI.class); 
        URL urlMock = PowerMockito.mock(URL.class); 
        
        PowerMockito.whenNew(URI.class).withAnyArguments().thenReturn(uriMock); 
        Mockito.when(uriMock.toURL()).thenReturn(urlMock); 
        Mockito.when(urlMock.openStream()).thenReturn(fis); 
        
        ClassUnderTest testObject = new ClassUnderTest(); 
        InputStream is = testObject.method(false, uri);

        Assert.assertEquals(fis, is);
    }
}

This is working because the bytecode of the ClassUnderTest is modified and the defined mock is placed there instead of the creation of a real URI object.

Community
  • 1
  • 1
second
  • 4,069
  • 2
  • 9
  • 24