10

I'm trying to test a class which uses a calculator class with a number of static methods. I've successfully mocked another class in a similar way, but this one is proving more stubborn.

It seems that if the mocked method contains a method call on one of the passed in arguments the static method is not mocked (and the test breaks). Removing the internal call is clearly not an option. Is there something obvious I'm missing here?

Here's a condensed version which behaves the same way...

public class SmallCalculator {

    public static int getLength(String string){

        int length = 0;

        //length = string.length(); // Uncomment this line and the mocking no longer works... 

        return length;
    }
}

And here's the test...

import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;

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

import com.solveit.aps.transport.model.impl.SmallCalculator;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SmallCalculator.class})
public class SmallTester {

    @Test
    public void smallTest(){

        PowerMockito.spy(SmallCalculator.class);
        given(SmallCalculator.getLength(any(String.class))).willReturn(5);

        assertEquals(5, SmallCalculator.getLength(""));
    }
}

It seems there's some confusion about the question, so I've contrived a more 'realistic' example. This one adds a level of indirection, so that it doesn't appear that I'm testing the mocked method directly. The SmallCalculator class is unchanged:

public class BigCalculator {

    public int getLength(){

        int length  = SmallCalculator.getLength("random string");

        // ... other logic

        return length;
    }

    public static void main(String... args){

        new BigCalculator();
    }
}

And here's the new test class...

import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;

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

import com.solveit.aps.transport.model.impl.BigCalculator;
import com.solveit.aps.transport.model.impl.SmallCalculator;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SmallCalculator.class})
public class BigTester {

    @Test
    public void bigTest(){

        PowerMockito.spy(SmallCalculator.class);
        given(SmallCalculator.getLength(any(String.class))).willReturn(5);

        BigCalculator bigCalculator = new BigCalculator();
        assertEquals(5, bigCalculator.getLength());
    }
}
maccaroo
  • 819
  • 2
  • 12
  • 22
  • unless this piece of code/test is only for proof of concept that mocking framework fails to mock up something it is supposed to mock, there is a big confusion here. One should **NOT** mock up the class that's going under the test but rather she mocks up its dependencies so that she can test class's behavior. – Alp Jul 23 '15 at 12:52
  • In the case of this example, I get a NPE when string.length() is called. That's weird because it seems the argument is being replaced, but the actual method is not being mocked. – maccaroo Jul 23 '15 at 12:53
  • @Alp As mentioned, the original test is attempting to mock a class which is called from the actual class under test. The example is just there to show the failure. – maccaroo Jul 23 '15 at 12:54

4 Answers4

22

I've found the answer here https://blog.codecentric.de/en/2011/11/testing-and-mocking-of-static-methods-in-java/

Here's the final code which works. I've tested this approach in the original code (as well as the contrived example) and it works great. Simples...

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;

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

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SmallCalculator.class})
public class BigTester {

    @Test
    public void bigTest(){

        PowerMockito.mockStatic(SmallCalculator.class);
        PowerMockito.when(SmallCalculator.getLength(any(String.class))).thenReturn(5);

        BigCalculator bigCalculator = new BigCalculator();
        assertEquals(5, bigCalculator.getLength());
    }
}
maccaroo
  • 819
  • 2
  • 12
  • 22
1

Use anyString() instead of any(String.class).

When using any(String.class) the passed argument is null, as Mockito will return the default value for the reference type, which is null. As a result you get an exception.

When using the anyString(), the passed argument will be empty string.

Note, this explains why you get an exception, however you need to review the way you test your method, as explained in other comments and answers.

vtor
  • 8,989
  • 7
  • 51
  • 67
  • The fact is, the inner workings of the mocked method should never be considered, so the value of the string parameter shouldn't matter. – maccaroo Jul 23 '15 at 13:17
1

First of all, remove that line:

given(SmallCalculator.getLength(any(String.class))).willReturn(5);

Since you are testing the same method. You don't want to mock the method that you're testing.

Secondly, modify your annotation to:

@PrepareForTest({ SmallCalculator.class, String.class})

Lastly, add the mock for the length(); like this:

given(String.length()).willReturn(5);

I think it will do ;)

JFPicard
  • 5,029
  • 3
  • 19
  • 43
  • I'm not testing the same method as I'm mocking. I've updated the example to illustrate this better. – maccaroo Jul 23 '15 at 13:11
0

If you do not want that actual method being invoked. Instead of

when(myMethodcall()).thenReturn(myResult);

use

doReturn(myResult).when(myMethodCall());

It is mocking magic and it is difficult to explain why it is actually works.

Other thing you forgot is mockStatic(SmallCalculator.class)

And you do not need PowerMockito.spy(SmallCalculator.class);

talex
  • 17,973
  • 3
  • 29
  • 66