模拟私有方法内部使用的静态方法,而不使用 PowerMock。

huangapple 未分类评论47阅读模式
英文:

Mock a static method used inside a private method without using powermock

问题

我想嘲笑一个类的私有方法中使用的静态方法,并返回特定的值。

以下是一个示例:

public class Test {

    private String encodeValue(String abc) {
        try {
            return MyMockedClass.encode(...);  // 在这里调用静态方法
        } catch (UnsupportedEncodingException e) {
            throw InvalidValueException.create("Error converting");
        }
    }
}

public class MyMockedClass {
    public static String encode(String s, String enc) throws UnsupportedEncodingException {
        // 在这里实现你想要的模拟行为并返回特定的值
    }
}

你可以创建一个类(在这里是MyMockedClass),其中包含一个与URLEncoder.encode方法相同签名的静态方法,用于模拟所需的行为并返回特定的值。然后,在你的Test类中,将私有方法中的URLEncoder.encode调用替换为对MyMockedClass.encode的调用。

这种方法避免了使用PowerMock,而是使用了一个专门为模拟所创建的辅助类。这种模式在许多测试框架中都是可行的,不仅限于PowerMock。

英文:

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);
}

答案1

得分: 0

模拟私有方法和静态方法是JMockit相对于其他模拟框架的主要优势之一。

你所称之为“Test”的类实际上是“ClassUnderTest”,所以抱歉,但是对“Test”的测试是“TestTest” 模拟私有方法内部使用的静态方法,而不使用 PowerMock。

public class TestTest {
  @Tested
  public Test cut;

  @Test
  public void testencodeValue() {

     // 模拟静态方法
     new MockUp<URLEncoder>() {
       @Mock
       String encode(String s, String enc) {
          return "JMockit FTW";
       }
     };

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

话虽如此,测试私有方法有点麻烦。一般来说,如果一个方法值得测试,那么几乎肯定值得暴露出来。使方法值得测试的相同标准意味着将来某个时候会有人想要覆盖你的实现并提供稍微不同的实现。让他们的工作变得简单,将其声明为受保护的。让你的(测试)工作变得简单,也做同样的事情。

英文:

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" 模拟私有方法内部使用的静态方法,而不使用 PowerMock。

public class TestTest {
  @Tested
  public Test cut;

  @Test
  public void testencodeValue() {

     // Mock the static
     new MockUp&lt;URLEncoder&gt;() {
       @Mock
       String encode(String s, String enc) {
          return &quot;JMockit FTW&quot;;
       }
     };

    // invoke the private method
		final Method method = MethodReflection.findCompatibleMethod(Test.class, &quot;encodeValue&quot;, new Class&lt;?&gt;[] { String.class });
		final String res = MethodReflection.invoke(cut, method);
    
     assertEquals(&quot;JMockit FTW&quot;, 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.

huangapple
  • 本文由 发表于 2020年7月23日 22:13:34
  • 转载请务必保留本文链接:https://java.coder-hub.com/63056338.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定