1

I have a scenario where I have an abstract class similar to the below. I want to mock out the RandomNumberGenerator instance that is supplied via the constructor and I want to write a unit test to test the logic in the doSomethingRandomLotsOfTimes method.

Is there a way I can do this with mockito/another framework?

public abstract class MyClass {

    private final RandomNumberGenerator randomNumberGenerator;

    public MyClass(RandomNumberGenerator randomNumberGenerator) {
        this.randomNumberGenerator = randomNumberGenerator;
    }

    public abstract void doSomething(int x);

    public void doSomethingRandomLotsOfTimes() {
        for(int i=0; i<10; i++) {
            int r = randomNumberGenerator.next();
            doSomething(r);
        }
    }
}

My Test

@Test
public void testItDoesSomething10Times() {
    // Given
    when(randomNumberGenerator.next()).thenReturn(0);
    MyClass myClass = ...

    // When
    myClass.doSomethingRandomLotsOfTimes();

    // Then
    // Assert Something
}
Eduardo
  • 6,900
  • 17
  • 77
  • 121
  • http://stackoverflow.com/questions/1087339/using-mockito-to-test-abstract-classes – Sasha Shpota Mar 31 '17 at 15:41
  • @OleksandrShpota I think there's a difference as those questions dont involve a constructor parameter which needs to be mocked – Eduardo Mar 31 '17 at 15:45
  • 1
    Why do you need to mock this class? Use the real class, with a mocked RNG. Also, this looks like an example of where composition should be preferred to inheritance: `doSomething` is simply an [`IntConsumer`](https://docs.oracle.com/javase/8/docs/api/java/util/function/IntConsumer.html): pass one of those into the constructor instead. Then you can unit test the `IntConsumer`, and unit test that `MyClass` invokes the `IntConsumer`. – Andy Turner Mar 31 '17 at 15:46
  • I'd rather keep the tests specific to logic in this class in a `MyClassTest` and let tests of sub classes only worry about the logic in themselves – Eduardo Mar 31 '17 at 15:48
  • 1
    *"I'd rather keep the tests specific to logic in this class"* this supports the *favor composition over inheritance* suggestion of Andy. Make thid a real class, give the Method `doSomethingRandomLotsOfTimes` a functional interface parameter and pass it as dependency to classes who need it. – Timothy Truckle Apr 02 '17 at 17:58

2 Answers2

4

Your class is much more readily implemented via composition:

public final class MyClass {

    private final RandomNumberGenerator randomNumberGenerator;
    private final IntConsumer somethingDoer;

    public MyClass(
            RandomNumberGenerator randomNumberGenerator,
            IntConsumer somethingDoer) {
        this.randomNumberGenerator = randomNumberGenerator;
        this.somethingDoer = somethingDoer;
    }

    public void doSomethingRandomLotsOfTimes() {
        for(int i=0; i<10; i++) {
            int r = randomNumberGenerator.next();
            somethingDoer.consume(r);
        }
    }
}

Now, you can write a unit test to assert that MyClass works, and it calls the somethingDoer the expected number of times; and you can write unit tests to assert that your IntConsumers work.

But you don't have to worry about things like:

  • The fragile base class problem.
  • Careless/malicious implementations of MyClass invoking doSomethingRandomLotsOfTimes() from doSomething() (well, maybe you do, it's just a lot harder...)
  • "Designing and documenting for inheritance"

etc.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 1
    Thank you very much sir --- wishing you a nice evening, good night, and may the next days greet you with great questions to answer! – GhostCat Mar 31 '17 at 19:23
3

There is no way to directly instantiate an abstract class. You must instantiate a non-abstract class that extends the abstract class.

Declare a non-abstract class that extends the abstract class in the unit test class (for example public static class blam extends MyClass) and provide empty implementations of all abstract methods.

Once you have a class that you can instantiate, create a spy of the RandomNumberGenerator class. A spy allows you to test that methods in the spy are called.

Create an instance of blam and pass the spy RandomNumberGenerator as the constructor parameter.

DwB
  • 37,124
  • 11
  • 56
  • 82
  • I would quite like to test some scenarios of how the logic in MyClass handles doSomething() throwing exceptions etc, is this possible with the method you describe? – Eduardo Mar 31 '17 at 15:51
  • @Ed0906 sure, just implement the abstract method to throw an exception. – Andy Turner Mar 31 '17 at 15:52
  • 1
    You can create a mock with: `MyClass myClass = mock(MyClass.class, Mockito.CALLS_REAL_METHODS);` To throw an exception: `doThrow(new Exception()).when(myClass).doSomething(anyInt());` –  Mar 31 '17 at 16:05
  • Once you have a spy, it is exactly like the mock with Calls_Real_methods that Hugo mentions. With a spy, you can mock the individual methods and throw as you need. – DwB Apr 01 '17 at 21:27