Using Mockito to Test Abstract Classes

Using Mockito to test abstract classes

The following suggestion let's you test abstract classes without creating a "real" subclass - the Mock is the subclass.

use Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), then mock any abstract methods that are invoked.

Example:

public abstract class My {
public Result methodUnderTest() { ... }
protected abstract void methodIDontCareAbout();
}

public class MyTest {
@Test
public void shouldFailOnNullIdentifiers() {
My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
Assert.assertSomething(my.methodUnderTest());
}
}

Note: The beauty of this solution is that you do not have to implement the abstract methods, as long as they are never invoked.

In my honest opinion, this is neater than using a spy, since a spy requires an instance, which means you have to create an instantiatable subclass of your abstract class.

Testing abstract class with Mockito. How?

Why not just:

    List<String> cutsomList = ....
Foo mock = Mockito.mock(Foo.class);
Mockito.when(mock.getItems()).thenReturn(customList);
Mockito.when(mock.process()).thenCallRealMethod();

Or (for void)

    doCallRealMethod().when(mock.process()).voidFunction();

Can an abstract class be mocked using mockito?

Well, this code below works fine, just tell me if I need to add some comments to explain what I wrote, ok? (hey, I am using Mockito 1.10.8):

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

abstract class AbstractClassToTest {
public abstract String doSomething();
}

class ConcreteClass {

private String something;

public ConcreteClass(AbstractClassToTest aClass){
this.something = aClass.doSomething();
}

public String getSomething(){
return this.something;
}
}

@RunWith(MockitoJUnitRunner.class)
public class TempTest {

@Mock
private AbstractClassToTest myClass;

@Test
public void canAbstractClassToTestBeMocked() {
String expectedResult = "hello world!";
Mockito
.when(myClass.doSomething())
.thenReturn(expectedResult);

String actualResult = myClass.doSomething();

Assert.assertEquals(expectedResult, actualResult);
}

@Test
public void canConcreteClassBeInstantiatedWithMock() {
String expectedResult = "hello world!";
Mockito
.when(myClass.doSomething())
.thenReturn(expectedResult);

ConcreteClass concrete = new ConcreteClass(myClass);

String actualResult = concrete.getSomething();

Assert.assertEquals(expectedResult, actualResult);
}
}

Test abstract class which extends another class

Mockito can't mock abstract classes. But you can have a subclass for your test purpose which implement your class B and then spy on it.

@RunWith(MockitoJUnitRunner.class)
public class BTest {

@InjectMocks
@Spy
private FakeB b;

@Test
public void testSomething() {
when(b.convert(any())).thenReturn(something);
}

public static class FakeB extends B {
protected Object convert(Object someobject) {
return null;
}
}
}

With this approach you can easily mock your abstract methods and test non-abstract ones in your abstract classes. Of course the downside is that you have to crate a fake test classes that subtypes your abstract classes. But I think it shouldn't be a big issue.

Mocking an abstract class and injecting classes with Mockito annotations?

I am not aware of any way to go about this, for one clear reason: @InjectMocks is meant for non-mocked systems under test, and @Mock is meant for mocked collaborators, and Mockito is not designed for any class to fill both those roles in the same test.

Bear in mind that your @Mock(CALLS_REAL_METHODS) declaration is inherently dangerous: You're testing your AbstractClassUnderTest, but you are not running any constructors or initializing any fields. I don't think you can expect a test with this design to be realistic or robust, no matter what annotations can or cannot do for you. (Personally, I was previously in favor of real partial mocks of abstract classes as a "tool in the toolbox", but I'm coming around to thinking they're too far removed from reality to be useful.)

Were I in your position, I would create a small override implementation for testing:

@RunWith(JUnit4.class) public class AbstractClassTest {
/** Minimial AbstractClass implementation for testing. */
public static class SimpleConcreteClass extends AbstractClass {
public SimpleConcreteClass() { super("foo", "bar", 42); }
@Override public void abstractMethod1() {}
@Override public String abstractMethod2(int parameter) { return ""; }
}

@InjectMocks SimpleConcreteClass classUnderTest;
@Mock mockClassA;
@Mock mockClassB;
}

At this point, you have a simple and predictable AbstractClass implementation, which you can use even without a mocking framework if you just wanted to test that AbstractClass has the same API for extension that it did before. (This is an often-overlooked test for abstract classes.) You can even extract this, as it may be useful for other testing: Should you want to override the abstract behavior for a single test class, you can create an anonymous inner class with just a single method override, or you can set classUnderTest = spy(classUnderTest); to set up Mockito proxying and the behavior you want.

(Bear in mind that @InjectMocks and @Spy can't be used reliably together, as documented in this GitHub issue and the Google Code and mailing list threads to which it links.)

Mock the rest of an abstract class but call the real methods in it?

Using a technique like in this SO answer, you can use CALLS_REAL_METHODS as a default answer—or, as you suggested, you can use the default answer (RETURNS_DEFAULTS) and individually stub certain methods to call your fake. Because you want Mockito's behavior to show through, I'd recommend the latter.

There's an equivalent to thenCallRealMethod for void methods: doCallRealMethod. You need to start with do because when(T value) has no value to take [and subsequently ignore].

abstract class FooForTest implements Foo {

public static Foo create() {
FooForTest mockFoo = mock(FooForTest.class);
mockFoo.map = new HashMap<>();
when(mockFoo.get(any())).thenCallRealMethod();
doCallRealMethod().when(mockFoo).add(any(), any());
return mockFoo;
}

Map<Object, Object> map = new ...

@Override
void add(Object key, Object value) {
map.put(key, value);
}

@Override
Object get(Object key) {
map.get(key);
}
}

Without complicated reflection, I can't think of any way to automatically let Mockito call real methods for unimplemented methods only. If you were to write an Answer for that, though, you could use it for all methods by passing it into the call to mock. You will also need to initialize your field explicitly in your create method, as the mock is not a real object and will not be properly initialized.

Another alternative would be to have empty method stubs, and use spy() instead; this would solve some of the initialization concerns.


This technique is called partial mocking, and beware that it can act as a code smell—specifically, that the class under test is violating the Single Responsibility Principle. In any case you should be very careful when adding methods to widely-implemented interfaces, for all of these reasons, and consider AbstractFoo (or a fully-implemented FakeFoo) as sensible upgrades later.

Can dart Mockito mock abstract classes?

If the example code from the mockito_builder repo is any indication, it seems like in order to build mocks of abstract classes, there needs to be a concrete implementation of that class somewhere.

My guess is that, when the Dart code is compiled (JIT or otherwise) and loaded into the build_runner, tree-shaking will see that the abstract class has no implementations nor any other references of any kind, so it removes the class from the compiled code. By the time build_runner can do its thing, the abstract class effectively no longer exists.

So in theory, all you need to do is make a placeholder implementation of your class and the code generation will work:

abstract class LoginService {
Future<void> login(String username, String password);
}

class LoginServiceImpl extends LoginService {
@override
Future<void> login(String username, String password) {
throw UnimplementedError();
}
}


Related Topics



Leave a reply



Submit