1

I have this below method.

 protected static String encode(String url) {
    try {
      url = URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
    } catch (Exception e) {
      LOGGER.warn("exception occured while encoding url {}", url);
    }
    return url;
  }

I am unable to provide a junit test for this because I can't mock URLEncoder. There are 2 possible outcomes of this method

  • encoded url
  • original url if there is some exceptions

I am able to create a test method for first outcome. how would you create test method for the second outcome?

mufeez-shaikh
  • 179
  • 2
  • 14
  • What kind of exception are you expecting? – shmosel May 08 '19 at 23:20
  • UnsupportedEncodingException - the catch should have this exception – mufeez-shaikh May 08 '19 at 23:23
  • a) If you're expecting a specific exception, catch it specifically. Don't just catch `Exception`. b) You'll never get an `UnsupportedEncodingException` from this code. You can just throw a `RuntimeException` or `AssertionError`. – shmosel May 08 '19 at 23:25
  • 2
    *Why* would you test it? Oracle have already tested it, and millions of users have been using it for twenty+ years. It's tested. Don't test the platform. Test your application. – user207421 May 08 '19 at 23:30
  • because, we are maintaining test coverage ratio. could not find a way to bypass it which is why are testing it. @user – mufeez-shaikh May 08 '19 at 23:39
  • I understand this sounds silly. I just want to get more idea on the junits conditions. encode method throws UnsupportedEncodingException because of which i have to add the try catch block. this creates additional branch for testing and affects the branch coverage ratio. how would you go about it ? – mufeez-shaikh May 09 '19 at 01:32
  • No. You just want to solve the actual problem, which is how to tell your tool that you don't have to test the JDK. – user207421 May 09 '19 at 02:11
  • 2
    "because, we are maintaining test coverage ratio" - the point of testing is supposed to be to improve code quality, not satisfy meaningless metrics. –  May 09 '19 at 02:12
  • 2
    @user207421 He is not trying to test `URLEncoder`. He has a class that uses `URLEncoder` and he wants to test that class. So he is looking for a way to mock `URLEncoder`. Be nice to newcomers and don't attack them. – Behrang May 09 '19 at 02:15
  • Possible duplicate of [Mocking static methods with Mockito](https://stackoverflow.com/questions/21105403/mocking-static-methods-with-mockito) – Behrang May 09 '19 at 02:17
  • But this "test" does not test anything - if the exception is thrown by mock code, then all the test shows is that *try ... throw X ... catch* can catch X, for all X. It says nothing about what URLencoder will actually do. –  May 09 '19 at 02:18
  • 2
    As @Behrang pointed out, OP is not trying to test "someone else's code", they are trying to test the above static method that they have written, not the `URLEncoder` class. For the above method, `URLEncoder` is an external dependency so for an unit test, it is perfectly fine to not depend on its implementation. It is also valid to test that the method returns the input in case of an exception, not `null` or blank string or some hard coded text. – Marimuthu Madasamy May 09 '19 at 02:50
  • @MarimuthuMadasamy by your logic, you shouldn't be depending on any of the classes from the JDK in your unit tests because they are external, as in, not your code. – Abhijit Sarkar May 09 '19 at 04:42
  • @AbhijitSarkar I would definitely do it even for some JDK classes especially if they have side effects or are non-deterministic. For example, changing `LocalDateTime.now()` in the code to `LocalDateTime.now(Clock)` and then mocking `Clock` class to return fixed time in unit tests. – Marimuthu Madasamy May 10 '19 at 01:58
  • @MarimuthuMadasamy sure, beat yourself up. – Abhijit Sarkar May 10 '19 at 02:01

3 Answers3

1

You could use PowerMockito to mock static methods. Assuming the static method in your post is in a class called HelloWorld, here are the two tests where the first test is testing the positive case and the second test is testing the exception case:

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

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.powermock.api.mockito.PowerMockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloWorld.class)
public class HelloWorldTest {

    @Test
    public void encode_returnsEncoded() throws UnsupportedEncodingException {
        // given
        mockStatic(URLEncoder.class);
        when(URLEncoder.encode(any(String.class), any(String.class)))
            .thenReturn("testUrlEncoded");

        // when
        String encoded = HelloWorld.encode("testUrl");

        // then
        assertThat(encoded).isEqualTo("testUrlEncoded");
    }

    @Test
    public void encode_returnsInputOnException() {
        // given
        mockStatic(URLEncoder.class);
        doThrow(new Exception("exception from test"))
            .when(URLEncoder.class);

        // when
        String encoded = HelloWorld.encode("testUrl");

        // then
        assertThat(encoded).isEqualTo("testUrl");
    }

}
Marimuthu Madasamy
  • 13,126
  • 4
  • 30
  • 52
1

The fundamental theorem of software engineering (FTSE) is a term originated by Andrew Koenig to describe a remark by Butler Lampson attributed to the late David J. Wheeler:

"We can solve any problem by introducing an extra level of indirection."

[...]

The theorem is often expanded by the humorous clause "…except for the problem of too many levels of indirection," referring to the fact that too many abstractions may create intrinsic complexity issues of their own. (Source: Wikipedia)

So let's say there's a class that has a static method named encode:

public final class UrlHelper {
    protected static String encode(String url) {
        try {
            url = URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
        } catch (Exception e) {
            LOGGER.warn("exception occured while encoding url {}", url);
        }

        return url;
    }
}

and your code depends on it:

public class MyClass {
    public void doSomething(String someUrl) {
        // ...
        String encodedUrl = UrlHelper.encode(someUrl);
        // ...
    }
}

and you want to test MyClass.doSomething(String someUrl) but you want to mock UrlHelper.encode(someUrl). One option is to define another class such as

public final class MyUrlHelper {
    protected String encode(String url) {
        return UrlHelper.encode(someUrl); 
    }
}

As MyUrlHelper.encode(String url) is not static, you can refactor your original code and test it by relying on dependency injection and mocking the non-static MyUrlHelper.encode(String url):

// Refactored
public class MyClass {

    private MyUrlHelper myUrlHelper;

    public UrlHelper(MyUrlHelper muUrlHelper) {
        this.myUrlHelper = myUrlHelper;
    }


    public void doSomething(String someUrl) {
        // ...
        String encodedUrl = myUrlHelper.encode(someUrl);
        // ...
    }
}

// Test
@Test
public void myTest() {
    // setup myUrlHelper and configure it
    MyUrlHelper myUrlHelper = mock(MyUrlHelper.class);
    when(myUrlHelper.encode(...)).thenReturn(...);


    // inject
    MyClass myObject = new MyClass(myUrlHelper);

    // stimulate
    myObject.doSomething("...")
}

Another option is to use Mockito using the PowerMockRunner as explained by @Marimuthu Madasamy.


However, I don't see any benefit in mocking UrlHelper.encode or URLEncoder.encode here. It is not an external system (a database, a file system, a message broker, a SOAP API, a REST API, etc.) so I don't see any gains by mocking it.

Behrang
  • 46,888
  • 25
  • 118
  • 160
0

If you are willing to use Lombok, I got a practical approach for you:

@lombok.Generated // Function won't raise up in Jacoco coverage report, see https://stackoverflow.com/a/56327700/1645517
@lombok.SneakyThrows(UnsupportedEncodingException.class) // Suppress handling this exception, see https://projectlombok.org/features/SneakyThrows
private static String urlEncode(final String valueToEncode) {
  return URLEncoder.encode(valueToEncode, StandardCharsets.UTF_8.name());
}
  • Jacoco will ignore this method
  • The UnsupportedEncodingException cannot be thrown here. Let Lombok handle this ugly circumstance.
Markus Schulte
  • 4,171
  • 3
  • 47
  • 58