Search code examples
junitmockingjmockit

How to mock private methods from Jmokit 1.49 version


I am using Junit 3.8.1 and updated Jmokit to 1.49

I have a project in which existing tests present with MockUp. Having private methods mocked. After updating Jmockit jar to 1.49 version getting error as follows

java.lang.IllegalArgumentException: Unsupported fake for private method

My Java class to test is

public class Foo {

String aVar;

public Foo(String str) {
    aVar = str;
}

private void concatStr(String append) {
    aVar = aVar.concat(append);
}

public void doSomeTask() {
    concatStr("Test");
}
}

and test class is

public class FooTest extends TestCase {
public FooTest(String testName) {
    super(testName);
}

public static Test suite() {
    return new TestSuite(FooTest.class);
}

public void test() {
    new MockUp<Foo>() {
        @Mock
        private void concatStr(String append) {
            Assert.assertEquals("Test", append);
        }
    };

    Foo foo = new Foo("demoString");
    foo.doSomeTask();
}
}

On console getting error as below

[INFO] Running org.test.jmokitupdate.FooTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.028 s <<< FAILURE! - in 
org.test.jmokitupdate.FooTest
[ERROR] test(org.test.jmokitupdate.FooTest)  Time elapsed: 0.025 s  <<< ERROR!
java.lang.IllegalArgumentException: Unsupported fake for private method 
Foo#concatStr(Ljava/lang/String;)V found
    at mockit.internal.faking.FakedClassModifier.visitMethod(FakedClassModifier.java:96)
    at mockit.asm.methods.MethodReader.readMethodBody(MethodReader.java:118)
    at mockit.asm.methods.MethodReader.readMethod(MethodReader.java:75)
    at mockit.asm.methods.MethodReader.readMethods(MethodReader.java:62)
    at mockit.asm.classes.ClassReader.readFieldsAndMethods(ClassReader.java:196)
    at mockit.asm.classes.ClassReader.accept(ClassReader.java:89)
    at mockit.internal.faking.FakeClassSetup.modifyRealClass(FakeClassSetup.java:80)
    at mockit.internal.faking.FakeClassSetup.redefineMethods(FakeClassSetup.java:61)
    at mockit.MockUp.redefineClass(MockUp.java:114)
    at mockit.MockUp.<init>(MockUp.java:78)
    at org.test.jmokitupdate.FooTest$1.<init>(FooTest.java:31)
    at org.test.jmokitupdate.FooTest.test(FooTest.java:31)

Solution

  • Earlier versions of JMockit allowed mocking private methods, and honestly, I thought it was a brilliant differentiator with other mocking-frameworks. Sadly, more recent versions have eliminated the ability to mock privates - became a warning in 1.45 and an exception in 1.47.

    There is no real official explanation, although supposition is that private methods should be so simple they do not need testing/mocking. By extension, if you are trying to access it for purposes of testing, then it should not be private. People (other than you) would likely want to also alter the behavior, and that your need to access it for test purposes is strongly suggesting the method ought to be accessible. Make it protected or package-private. FWIW, there are annotations like "@VisibleForTesting" that can be used to help indicate the intent.

    So you know, 1.47 also removed the "Deencapsulation" mechanism which was one of my favorite tools for inspecting/setting private data. Painful at the time I had to convert, because it littered my test code, but in hind sight, @Tested/@Injectable (the replacement) is way cleaner. As the maintainer indicates, JMockit is not intended as a way to get at privates, there are other frameworks that do that and no sense in doing the job that they do better. I switched over to Apache's commons-lang3 (FieldUtils/MethodUtils/etc), but other frameworks exist