-1

I wanted to mock a static method used inside a private method of a class and return a specific value.

 public class Test {

    private String encodeValue(String abc) {

    try {
        return URLEncoder.encode(...);
    } catch (UnsupportedEncodingException e) {
        throw InvalidValueException.create("Error converting");
    }
}

URLEncoder.encode ->encode is a static method inside URLEncoder.

In Test class using powermock works:

    PowerMock.mockStatic(URLEncoder.class);
    expect(URLEncoder.encode()).andThrow(new UnsupportedEncodingException());
    PowerMock.replay(URLEncoder.class);
    String encoded = Whitebox.invokeMethod(testMock,"encodeVaue","Apple Mango");

But i wanted to replace Powermock with any other mocking ways available. Is there a way to mock the above class.

URL Encoder class:

 /**
 * Translates a string into {@code application/x-www-form-urlencoded}
 * format using a specific encoding scheme. This method uses the
 * supplied encoding scheme to obtain the bytes for unsafe
 * characters.
 * <p>
 * <em><strong>Note:</strong> The <a href=
 * "http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
 * World Wide Web Consortium Recommendation</a> states that
 * UTF-8 should be used. Not doing so may introduce
 * incompatibilities.</em>
 *
 * @param   s   {@code String} to be translated.
 * @param   enc   The name of a supported
 *    <a href="../lang/package-summary.html#charenc">character
 *    encoding</a>.
 * @return  the translated {@code String}.
 * @exception  UnsupportedEncodingException
 *             If the named encoding is not supported
 * @see URLDecoder#decode(java.lang.String, java.lang.String)
 * @since 1.4
 */
public static String encode(String s, String enc)
    throws UnsupportedEncodingException {

    boolean needToChange = false;
    StringBuffer out = new StringBuffer(s.length());
    Charset charset;
    CharArrayWriter charArrayWriter = new CharArrayWriter();

    if (enc == null)
        throw new NullPointerException("charsetName");

    try {
        charset = Charset.forName(enc);
    } catch (IllegalCharsetNameException e) {
        throw new UnsupportedEncodingException(enc);
    } catch (UnsupportedCharsetException e) {
        throw new UnsupportedEncodingException(enc);
    }

    for (int i = 0; i < s.length();) {
        int c = (int) s.charAt(i);
        //System.out.println("Examining character: " + c);
        if (dontNeedEncoding.get(c)) {
            if (c == ' ') {
                c = '+';
                needToChange = true;
            }
            //System.out.println("Storing: " + c);
            out.append((char)c);
            i++;
        } else {
            // convert to external encoding before hex conversion
            do {
                charArrayWriter.write(c);
                /*
                 * If this character represents the start of a Unicode
                 * surrogate pair, then pass in two characters. It's not
                 * clear what should be done if a bytes reserved in the
                 * surrogate pairs range occurs outside of a legal
                 * surrogate pair. For now, just treat it as if it were
                 * any other character.
                 */
                if (c >= 0xD800 && c <= 0xDBFF) {
                    /*
                      System.out.println(Integer.toHexString(c)
                      + " is high surrogate");
                    */
                    if ( (i+1) < s.length()) {
                        int d = (int) s.charAt(i+1);
                        /*
                          System.out.println("\tExamining "
                          + Integer.toHexString(d));
                        */
                        if (d >= 0xDC00 && d <= 0xDFFF) {
                            /*
                              System.out.println("\t"
                              + Integer.toHexString(d)
                              + " is low surrogate");
                            */
                            charArrayWriter.write(d);
                            i++;
                        }
                    }
                }
                i++;
            } while (i < s.length() && !dontNeedEncoding.get((c = (int) s.charAt(i))));

            charArrayWriter.flush();
            String str = new String(charArrayWriter.toCharArray());
            byte[] ba = str.getBytes(charset);
            for (int j = 0; j < ba.length; j++) {
                out.append('%');
                char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
                // converting to use uppercase letter as part of
                // the hex value if ch is a letter.
                if (Character.isLetter(ch)) {
                    ch -= caseDiff;
                }
                out.append(ch);
                ch = Character.forDigit(ba[j] & 0xF, 16);
                if (Character.isLetter(ch)) {
                    ch -= caseDiff;
                }
                out.append(ch);
            }
            charArrayWriter.reset();
            needToChange = true;
        }
    }

    return (needToChange? out.toString() : s);
}
Nila
  • 17
  • 1
  • 3
  • Please provide the details of that `URLEncoder` class. Under what circumstances is throwing an exception possible? – chrylis -cautiouslyoptimistic- Jul 23 '20 at 14:39
  • hi @chrylis -cautiouslyoptimistic This class are used as external libraries in the project and its a read only file. I had updated the class code above. Kindly have a look and provide your valuable suggestions – Nila Jul 25 '20 at 14:35

1 Answers1

0

Mocking privates and statics is one of the chief strengths of JMockit over other mocking frameworks.

The class you call "Test" is really the "ClassUnderTest", so apologies, but the test of "Test" is "TestTest" :)

public class TestTest {
  @Tested
  public Test cut;

  @Test
  public void testencodeValue() {

     // Mock the static
     new MockUp<URLEncoder>() {
       @Mock
       String encode(String s, String enc) {
          return "JMockit FTW";
       }
     };

    // invoke the private method
        final Method method = MethodReflection.findCompatibleMethod(Test.class, "encodeValue", new Class<?>[] { String.class });
        final String res = MethodReflection.invoke(cut, method);
    
     assertEquals("JMockit FTW", res);
  }
}

That said, testing privates is sort of a PITA. I am generally of the mind that if a method is worth testing, it is almost certainly worth exposing. The same criteria that make the method worth testing means that somebody-somewhere-someday will want to override your implementation and provide a slightly alternative one. Make their job easy, and make it protected. Make your (testing) job easy, and do the same thing.

Jeff Bennett
  • 996
  • 7
  • 18
  • Thank you for your reply, but on using @Mock - it says the annotation are not allowed here. Can you kindly let me know where should this annotation be used. – Nila Jul 26 '20 at 14:59
  • Also would like to know if its possible to throw a particular exception expect(URLEncoder.encode()).andThrow(new UnsupportedEncodingException()); - like this in JMockit – Nila Jul 26 '20 at 14:59
  • make sure you are using the latest version of JMockit, and make sure it is really using @mockit.Mock (I suspect you are picking up Mock from another framework). – Jeff Bennett Jul 26 '20 at 17:56
  • Yes, in the MockUp above, you can certainly throw instead of return – Jeff Bennett Jul 26 '20 at 17:56
  • import org.mockito.Mock; testImplementation 'org.jmockit:jmockit:1.48.x' testCompile group: 'org.jmockit', name: 'jmockit', version: '1.48' I have used these dependencies and import still i face the issue. Can you help? – Nila Jul 27 '20 at 06:44