3

I'm attempting to mock an abstract class, but from what I've seen, I don't think it's possible. We have some classes that use generics, that must extends a specific abstract class. There's a whole group of them and they have been mocked successfully. The abstract class has one method that deals with returning the generic and looks like this:

public abstract class ChildPresenter <T extends ChildView> {
    private T view;
    
    public abstract T getView();
}

The class we are testing has the following in it:

public class ParentPresenter {
    private ConcreteChildPresenter1 childPresenter1;
    private ConcreteChildPresenter2 childPresenter2;
    private ConcreteChildPresenter3 childPresenter3;
    private ConcreteChildPresenter4 childPresenter4;
    List<ChildPresenter> childPresenters;
}

In the constructor, these classes are injected in, using Google Guice, set to the variables, and added to the list of child presenters.

The method under test is one that iterates over all of the childPresenters objects and runs the method getView().

I attempted it this way in my test class:

public class ParentPresenterTest {
    private ConcreteChildPresenter1 childPresenter1;
    private ConcreteChildPresenter2 childPresenter2;
    private ConcreteChildPresenter3 childPresenter3;
    private ConcreteChildPresenter4 childPresenter4;
    private List<ChildPresenter> childPresenters;

    //This is an abstract class 
    private ChildView childView;

    @BeforeTest
    public void createInjector() {
        Guice.createInjector(...//Creates a fake module and does binding for the variables mentioned earlier
            //e.g.
            childPresenter1 = mock(ConcreteChildPresenter1.class);
            binder.bind(ConcreteChildPresenter1.class).toInstance(childPresenter1);
            //e.t.c for other variables
            
            //Same for child view
            childView = mock(ChildView.class);
            binder.bind(ChildView.class).toInstance(childView);
        }

        childPresenters = new ArrayList<ChildPresenter>();
        childPresenters.add(childPresenter1);
        //Add all child presenters
        
        for(ChildPresenter childPresenter : childPresenters) {
            when(childPresenter.getView()).thenReturn(childView);
        }
    }
}

The problem happens at the line when(childPresenter.getView()).thenReturn(childView); as Mockito complains with the following message:

org.mockito.exceptions.misusing.WrongTypeOfReturnValue:

ChildView$$EnhancerByMockitoWithCGLIB$$2f6a4bd5

cannot be returned by getView() getView() should return ConcreteChildView1

*** If you're unsure why you're getting above error read on. Due to the nature of the syntax above problem might occur because:

  1. This exception might occur in wrongly written multi-threaded tests. Please refer to Mockito FAQ on limitations of concurrency testing.

  2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

Which I can understand, but it seems a waste to mock each individual concrete ChildView when all I want to do is confirm the mocked ChildView called a single method using the following:

verify(childView, atLeast(childPresenters.size())).getView();

Is there another way to do this? Can I somehow use mocked abstract classes in place of the concrete ones?

EDIT Here is a concrete version of how the getView() method is implemented:

public ConcreteChildPresenter1<ConreteChildView1> {
    
    @Override
    public ConreteChildView1 getView() {
        view.buildView();
        return view;
    }
}

And the abstract ChildView class that all child views extend:

public abstract ChildView {

    public abstract void buildView();
}
Community
  • 1
  • 1
Draken
  • 3,134
  • 13
  • 34
  • 54
  • 2
    thenAnswer() with a cast might work –  Jun 28 '16 at 11:38
  • Hmm maybe you could insert real views and mock there methods which you don't want. – wake-0 Jun 28 '16 at 11:40
  • Have you already tried: doReturn(childView).when(childPresenter).getView(); – wake-0 Jun 28 '16 at 11:47
  • @RC. `thenAnswer()` works for the binding, but then running the tests I get the exception: `java.lang.ClassCastException: ChildView$$EnhancerByMockitoWithCGLIB$$37396a9a cannot be cast to ConcreteView1` – Draken Jun 28 '16 at 11:51
  • @KevinWallis For your first comment, that kind of defeats the whole objective, if I was doing that I would just create mocks of every single view and return those. I'd prefer it it I didn't have to. For your second comment, that has exactly the same issue. I don't see how reordering the `return()` `when()` would have made a positive influence. – Draken Jun 28 '16 at 11:52
  • @Draken so your `GetView` method does more than just returning the view? – wake-0 Jun 28 '16 at 11:57
  • @KevinWallis It calls a single method on the view, called `buildView()`, which is also available in the abstract class, then it returns the view. I'll update the quesiton with a concrete version of the child class – Draken Jun 28 '16 at 12:00

2 Answers2

2

Since each child presenter returns a view of a specific type, you can't, as you already understood, substitute them with mocks of the abstract class ChildView.

There is a way to get at runtime the concrete type of ChildView only if you provide a proper implementation like explained here: Get generic type of class at runtime

Then you may initialize the presenters' mocks in this way:

for(ChildPresenter childPresenter : childPresenters) {
    //this getter returns the needed runtime class
    when(childPresenter.getView()).thenReturn(mock(childPresenter.getViewType())); 
}
Community
  • 1
  • 1
Lorenzo Murrocu
  • 688
  • 4
  • 14
  • This is a better solution, it still presents the possible issue of not being able to verify exactly as I don't have the instances of the `childView` created. However I should be able to instead just confirm the type returned is of the correct instance type. I'll have a look and report back my findings. Thanks for the tip – Draken Jun 29 '16 at 06:34
  • Ah, this raises a further problem as we can't do that, these `ChildPresenter` objects are being injected in, so I have to have an empty constructor or one that I can inject the variables into. Generics and injection generally don't work well and this is one of those awkward times. There are ways around it, but they aren't easy and could cause confusion later on. I think I'll have to create mocks of each individual view, it's a shame, but I can't see an alternative. – Draken Jun 29 '16 at 06:47
  • I'll mark this as accepted, although it doesn't solve my problem exactly, I feel we're a little more niche. Thanks for the advice – Draken Jun 30 '16 at 12:28
1

Based on the refinements, the mocked ChildView is based on the wrong superclass. I think you can fix it at Guice injector:

Guice.createInjector(...//Creates a fake module and does binding for the variables mentioned earlier
    // ...
    //Same for child view
    childView = mock(ConcreteChildPresenter1.class); // this is the fix
    binder.bind(ChildView.class).toInstance(childView);
}
Draken
  • 3,134
  • 13
  • 34
  • 54
Tamas Rev
  • 7,008
  • 5
  • 32
  • 49
  • And for the other 3 `ChildView` base classes? This would bring me back to the case of having to create a mock for each individual concrete class, that I can do. However, I'm attempting to avoid that as I only need access to the abstract methods and means I can make the test more generic – Draken Jun 28 '16 at 12:45
  • According to the `ClassCastException` you have to create the right kind of mock. Unless you add a method like `Class getViewClass()` to the `ChildPresenter` class, you have to wire the mocks together manually. Bad news, I know. – Tamas Rev Jun 28 '16 at 13:02