Draken
Draken

Reputation: 3189

Mocking a generic class with an abstract version of said class

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();
}

Upvotes: 3

Views: 1671

Answers (2)

Tamas Rev
Tamas Rev

Reputation: 7166

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);
}

Upvotes: 1

Lorenzo Murrocu
Lorenzo Murrocu

Reputation: 688

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())); 
}

Upvotes: 2

Related Questions