2

I'm trying to test a public method, which calls a private method that returns a random integer to use in the public method.

I am finding it hard to write a test since my expected outpoint always varies to my result due to the random number generator. So i'm trying to make the private method return a specified value when called (in test).

Is this possible?

Method:

public String addLine(String arrayString){
    String[] numbers = arrayString.split(",");
    int startingSize = numbers.length;

    int newLineCounter = generateLineSpacing(startingSize);

    for (int index  =0;index <numbers.length; index++ ,newLineCounter--){
        if (newLineCounter == 0){
            numbers[index] = "\n," + numbers[index];
            newLineCounter = generateLineSpacing(startingSize);
        }
    }
    return StringUtils.arrayToCommaDelimitedString(numbers);
}


private int generateLineSpacing(int sizeOfArray) {
    Random random = new Random();

    //odd +1
    if (sizeOfArray % 2 == 1){
        sizeOfArray += 1;
    }

    //create new line in random position within range 1 - half array size
    return random.nextInt(sizeOfArray/2  -1 ) + 1;
}

Test:

@Test
public void shouldAddSymbolAtNewLineLocation(){
    //given
    String expected = "2,3,\n,4";
    String startingString = "2,3,4";

    //mock out random number generator to return 2

    //when
    String result = underTest.addLine(startingString);

    //then
    assertEquals(expected, result);
}
Moley
  • 113
  • 2
  • 10
  • 1
    Move the random number generator to an independent class, mock it in your unit test. – Elliott Frisch Oct 28 '17 at 01:32
  • @ElliottFrisch Seems so simple now, if you c answer I can approve! thanks – Moley Oct 28 '17 at 01:33
  • Someone beat me to an answer, but happy to help. – Elliott Frisch Oct 28 '17 at 01:43
  • @ElliottFrisch I've tried to implement it but since the public method now creates a new instance of the class where I moved the random number generator, the mocked return value isn't being used, since the mocked class isn't being called but a new instance each time? – Moley Oct 28 '17 at 01:58
  • Inject the rng class to the method (or use some other DI mechanism). – Elliott Frisch Oct 28 '17 at 01:59
  • @ElliottFrisch what do you mean sorry? – Moley Oct 28 '17 at 02:04
  • @ElliottFrisch sorry i missed your comment when I wrote my answer (or it was maybe in editing) – ghaith Oct 28 '17 at 02:16
  • @Moley do not instantiate your class in the method itself, pass it as a dependency to the class, meaning as a constructor parameter, that way in your test you just pass the alternative (mocked) implementation – ghaith Oct 28 '17 at 02:18
  • @ghaith Ah I understand sorry, the word dependency threw me, so pass it as a paramater into the method, yes? – Moley Oct 28 '17 at 02:48
  • 1
    yes, or to the class in the constructor, if you pass it to the class you can later make it easier to exchange for all methods that need random numbers. Eventually this also helps if you will be using Dependency Injection to instantiate the class – ghaith Oct 28 '17 at 02:52
  • The `addLine` method has two bugs - it doesn't work as is (I checked). – Rogério Oct 28 '17 at 13:46
  • @Rogério It works for me, want a link to the github project since would love the feedback? – Moley Oct 29 '17 at 00:22
  • Sure! I copy & pasted the code in the question into my IDE; bug 1 is that `"\n,"` should be `"\n"`, bug 2 is the `newLineCounter` loop is inserting line breaks after every line (except the last), when it should have been only one (I assume). – Rogério Oct 29 '17 at 13:36

1 Answers1

1

You cannot usually mock a private method. By declaring the method private you are making sure no one will modify its behaviour, a test should not do that either.

What you can do here is moving the random number generator into a Supplier passed as a dependency to the class, or as a method parameter, this can be mocked then.

As an altenative, this answer methions that you can use PowerMock to mock private methods, I have personally not used it but I generally think it should not be done (again because you are changing a private method).

ghaith
  • 346
  • 3
  • 7
  • There is no need to mock the private method (which would be a terrible idea anyway). `Random` can easily be mocked without any changes to the CUT, but even here there is no need to mock: instead, the test could (and should) just test the expected behavior of `addLine`, which simply requires a different form of assertion. – Rogério Oct 28 '17 at 13:45