0

I am trying to test a method which uses Random.nextDouble(). I need to set what this number will be to test the outcome of the method. I am unable to mock the random class. Here is the error message:

Mockito cannot mock this class: class java.util.Random.

Mockito can only mock non-private & non-final classes.

Here is a rough idea of what I am trying to do:

public string foo() {
   Random random = new Random();
   String word;

   if(random.nextDouble() <= 0.5) {
      word += "Hello";
   }
   if(random.nextDouble() <= 0.7) {
      word += "World";
   }
   return word;
}

And then the test class should look like this:

public class FooTest {

   @Test
   public void test {
      Mockito.mock(Random.class);
      when(random.nextDouble()).thenReturn(0.6);
      // Basically check that foo() returns "World" because the random number is 0.6
      Assertions.assertEquals("World", foo())
   }

}

Basically, I'm asking how to mock a method which is dependent on a random number. How do I do this? I assume it's mocking, but my attempt doesn't work. Thanks

  • 1
    google about dependency injection – Iłya Bursov Dec 13 '22 at 05:44
  • See [Why is my class not calling my mocked methods in unit test?](https://stackoverflow.com/q/74027324/112968) which even uses `Random` in its example. `Random` can be mocked. The problem seems that you never assign the `Mockito.mock(Random.class)` instance anywhere and call `when` on an instance/variable that doesn't exist. – knittl Dec 13 '22 at 07:30
  • When assigning it to a variable, the same error message pops up. It says mockito cannot mock the Random class. – Sebastian Sole Dec 13 '22 at 16:10

1 Answers1

0

Though the Random class is non-final and eligible for mocking, you don't need to mock it directly to insert a testing seam.

One way to do this is to create an overload for testing that uses DoubleSupplier (or a similar interface you create). Because you are supplying ("injecting") your method's dependency rather than supplying it opaquely within foo, this counts as dependency injection at the method level.

public String foo() {
   Random random = new Random();
   return foo(random::nextDouble);
}

@VisibleForTesting String foo(DoubleSupplier randomDoubleSupplier) {
   String word;

   if(randomDoubleSupplier.getAsDouble() <= 0.5) {
      word += "Hello";
   }
   if(randomDoubleSupplier.getAsDouble() <= 0.7) {
      word += "World";
   }
   return word;
}

Here @VisibleForTesting is a "Google Commons" documentation annotation that just indicates why this implementation is visible (package-private) rather than private or public. If you use the common src and test source directory structure, you could put the test in the same package as the file under test, allowing you to call the method without exposing it further.

Then, rather than statically mocking the system class Random, you can test your foo(DoubleSupplier) overload and supply your own DoubleSupplier implementation. This would be safe and easy to create using Mockito, as DoubleSupplier is a simple single-method interface and would not be subject to any concerns about static access, system classpath, visibility, finality, synthetic methods, or any other advanced Mockito concerns.

(Should you figure out why Mockito is resistant to your mock of Random.class, you can use an almost-identical technique to insert the Random class itself rather than a DoubleSupplier. However, because Random is a large class instead of a small interface, you may find your tests need more maintenance if your calls to Random change or if you try to mock a protected method like next(int). This might make mocking at the DoubleSupplier layer more appealing even if you're not forced that way.)

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251