Is it possible using Mockito and optionally Powermock to mock a superclass S
such that any calls to the superclass to S
(including calls to the S()
constructor) are mocked? So using the below example, if I replace S
with MockS
using Mockito, will the call to super()
use the constructor in MockS
?
class S {
S() {
// Format user's hard drive, call 911, and initiate self-destruct
}
}
class T extends S {
T() {
super();
}
}
class Test {
@Mock private S mockS;
new T(); // T's call to super() should call the mock, not the destructive S.
}
I've seen questions about mocking individual methods in S
or mocking only calls to super()
, and read that this is unsupported, but it's not clear whether or not I can mock the entire superclass.
With my current tests, when I try to mock S
, T
's call to super()
calls the real implementation, not the mock.
To work around this apparent limitation, I refactored my code, replacing inheritance with delegation, and I think I've ended up with a better design anyhow since the inheritance wasn't really necessary.
The new code looks like this. Mind you the code for the question was simplified, so the real classes have much more functionality.
class S {
S() {
// Format user's hard drive, call 911, and initiate self-destruct
}
}
class T {
T(S s) {} // Now T "has an S" instead of "is an S"
}
class Test {
@Mock private S mockS;
new T(s); // T's call to super() should call the mock, not the destructive S.
}
For those interested, using Guice and Android, the test looks more like this:
class T {
T(Activity activity, S s) {}
}
class Test {
@Mock Activity activity;
@Mock S mockS;
injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Activity.class).toInstance(activity);
bind(S.class).toInstance(mockS);
}}
);
T t = injector.getInstance(T.class);
}
I think this is possible with PowerMock only if the method on the child is different from the method on the superclass (i.e., you cannot mock the parent method if the child overrides that method). For a little more detail, you can look at the relevant bug report.
For PowerMock, check out Suppressing Unwanted Behavior page to see if it will be enough for your needs.
After much digging around, I ended up using JMockit for these tricky cases. Before I moved on to JMockit, I tried stubbing out all the places exceptions were thrown using suppression. In the end, I needed to override some methods, and not just suppress them, so I ended up abandoning it.
Example usage for Android case:
First, you mock out your superclass using the @MockClass
annotation:
@MockClass(realClass = Activity.class, instantiation = PerMockedInstance)
public class FakeActivity {
public Bundle mSavedInstanceState;
@Mock
public void $init() {}
@Mock
public void onCreate(Bundle savedInstanceState) {
mSavedInstanceState = savedInstanceState;
}
}
When activated, this class will replace the default constructor of Activity
with $init()
, and replace the onCreate
method with the one above. WIth android, the unit under test is derived from Activity (in my sample code, it is HelloTestActivity
). The test class looks like this:
public class HelloTestActivityTest3 extends AndroidTest {
@Tested
HelloTestActivity activity;
FakeActivity fakeActivity = new FakeActivity();
@Before
public void setupMocks()
{
Mockit.setUpMock(fakeActivity);
}
@Test
public void onCreate_bundle(@Mocked Bundle savedInstanceState)
{
// Try to access out-of-band information from the fake
activity.onCreate(savedInstanceState);
assertSame(savedInstanceState, fakeActivity.mSavedInstanceState);
}
}
The call Mockit.setupMock(fakeActivity)
replaces the super class with my instance of the fake. With this usage, you can access internal state of your fake class as well. If you don't need to override any methods with custom functionality, you can use other methods available from Mockit
class.
As rogerio pointed out in the comments below, mocking the Activity
class is the bare minimum. The following code demonstrates this.
public class HelloTestActivityTest4 {
@Tested
HelloTestActivity activity;
@Mocked
Activity base;
@Test
public void testOnCreate() throws Exception {
// Just make sure "Stub!" exception is not thrown.
activity.onCreate(null);
}
}
The declaration @Mocked Activity base;
causes all methods (excepting static initializers) of Activity
class and its superclasses to be mocked in the tests defined in HelloActivityTest4
.
What you can do is extract the 'dangerous' code in your superclass constructor into a non-private method, then use Mockito spy on your class T and override the behaviour in that extracted method.
This would of course violate encapsulation. Guava offers the VisibleForTesting annotation for such cases.