0

My test for Url connection worked correctly. Then after 1 month I ran all tests in the app and test for URL connection failed, I don't know why as I didn't change this class. Please, could someone look at this test?

The test now throws a java.lang.NullPointerException

The original idea for the test came from: https://programmingproblemsandsolutions.blogspot.com/2019/04/abstractmethoderror-is-thrown-on.html

test:

import org.junit.jupiter.api.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;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import static org.junit.jupiter.api.Assertions.assertTrue;

@RunWith(PowerMockRunner.class)
@PrepareForTest({URL.class})
class JsonOutputFromURLImplTest
{
    @Test
    void currencyValuesNBP() throws IOException
    {
        class UrlWrapper
        {
            private URL url;

            public UrlWrapper(String spec) throws MalformedURLException
            {
                url = new URL(spec);
            }

            private URLConnection openConnection() throws IOException
            {
                return url.openConnection();
            }
        }

        UrlWrapper url = PowerMockito.mock(UrlWrapper.class);
        HttpURLConnection httpURLConnection = Mockito.mock(HttpURLConnection.class);
        PowerMockito.when(url.openConnection()).thenReturn(httpURLConnection);
        assertTrue(url.openConnection() instanceof HttpURLConnection);
    }
}

Tested class:

@Service
@NoArgsConstructor
public class JsonOutputFromURLImpl implements JsonOutputFromURL
{

    @Override
    public String currencyValuesNBP(String APIUrl) throws ConnectionWithApiEX
    {
        String jsonOutput = null;

        try {
            URL url = new URL(APIUrl);
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.setRequestProperty("Content-Type", "application/json");

            InputStreamReader inputStreamReader = new InputStreamReader(httpURLConnection.getInputStream());
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            jsonOutput = bufferedReader.readLine();

            httpURLConnection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(jsonOutput != null)
            return jsonOutput;

        throw new ConnectionWithApiEX(APIUrl);
    }
}
java.lang.NullPointerException
    at com.bartosz.kolej.stock.service.impl.JsonOutputFromURLImplTest$1UrlWrapper.openConnection(JsonOutputFromURLImplTest.java:36)
    at com.bartosz.kolej.stock.service.impl.JsonOutputFromURLImplTest$1UrlWrapper.access$000(JsonOutputFromURLImplTest.java:25)
    at com.bartosz.kolej.stock.service.impl.JsonOutputFromURLImplTest.currencyValuesNBP(JsonOutputFromURLImplTest.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
    at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Lesiak
  • 22,088
  • 2
  • 41
  • 65
Bartosz Kolej
  • 97
  • 1
  • 12

1 Answers1

1

Short answer: mark your UrlWrapper.openConnection as public and you are good to go.

Secondly: you are not testing any method in your service class, it's code is irrelevant.

Thirdly: PowerMock can mock private methods, the reasons it doesn't work for you are quite complex.

  • you use Junit 5 annotations. The behaviour of the same test differs when using JUnit4 and JUnit5 annotations

Consider the following class

public static class MyClass {
    public String A() {
        System.out.println("A called");
        return "A";
    }

    private String B() {
        System.out.println("B called");
        return "B";
    }

}

With Junit4 annotation:

@org.junit.Test
public void testMockPrivateMethod() throws Exception {
    MyClass myClass = PowerMockito.mock(MyClass.class);
    PowerMockito.when(myClass.B()).thenReturn("mockB");
    //PowerMockito.when(myClass, "B").thenReturn("mockB");
    System.out.println(myClass.B());
}

Output:
mockB

With Junit5 annotation:

@org.junit.jupiter.api.Test
public void testMockPrivateMethod() throws Exception {
    MyClass myClass = PowerMockito.mock(MyClass.class);
    PowerMockito.when(myClass.B()).thenReturn("mockB");
    System.out.println(myClass.B());
}

Output:
B called

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

This is beacause you need bytecode instrumentation to mock private methods. Unfortunately, Junit5 engine does not use PowerMockRunner (it has it's own mechanism of Extensions). Note that you can use Junit4 annotations with Junit5, but you need to import vintage runner.

As you can see real method is being called. Unfortunately, you don't see the exception as your methods throws NPE.

Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • why people are making tests like that if they are irrelevant ? – Bartosz Kolej Jun 06 '19 at 11:57
  • I read the linked article, I think the message can be rephrased as (in fact the message is not clear, this is my interpretation): I you have a type that is not mockable, you can wrap it in a new class that is easily mockable. Imho general principle (make your code more testable) is fine. The approach works if you can isolate a small code snippet to replace to use the wrapper. If you cant, this will be problematic - you cannot easily pass the wrapper to methods that expect original object, – Lesiak Jun 06 '19 at 13:17