Search code examples
javaunit-testingmathjmockit

Why can't I mock Math using JMockit


I am running into a problem mocking out java.lang.Math using JMockit (1.21). See below for a simplified use of my actual class. Basically somewhere in my code I use Math.pow(...) and I whish to mock it.

public final class Calculator {
    public final double power(double base, double exponent) {
        return Math.pow(base, exponent);
    }
}

As for my test code, this test works.

@Test
public void simpleTestWithoutMock() {
    Calculator calculator = new Calculator();
    double result = calculator.power(2, 3);
    assertThat(result).isEqualTo(8);
}

This test fails.

@Test
public void simpleTestWithMock(@Mocked Math math) {
    new Expectations() {{ // This would be line 67 in below stracktrace
        Math.pow(2,3); result = 1;
    }};

    Calculator calculator = new Calculator();
    double result = calculator.power(2, 3);
    assertThat(result).isEqualTo(1);
}

The error message I get:

java.lang.NoClassDefFoundError: nl/endran/sandbox/CalculatorTest$1

        at nl.endran.sandbox.CalculatorTest.simpleTestWithMock(CalculatorTest.java:67)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:483)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:483)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
        Caused by: java.lang.ClassNotFoundException: nl.endran.sandbox.CalculatorTest$1
        at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 9 more

I am able to mock other system classes like System and RunTime like this, but Math just doesn't seem to work (nor does String for that matter). I know how to circumvent this is my tests, so I am not blocked or anything, but I cannot understand why Math cannot be mocked. Removing the Expectations block will not cause the NoClassDefFoundError error anymore. I suspect methods that have a signature like public static native to play a role in this, but I cannot find anything.


Solution

  • Because 1) methods from java.lang.Math (in particular, Math.min(a, b)) get called all the time by other classes from the JRE (for example, look inside java.lang.String), and 2) when JMockit mocks a class (with @Mocked, anyway), it gets mocked for the duration of the test, regardless of where the calls are coming from.

    Bottom line, don't mock low-level JRE classes unless you really know what you are doing. In this particular case, JMockit should probably deny the full mocking of java.lang.Math, as really there is no good reason (that I can see) for doing it.