Mock Abstract Class to Test

Bram Yeh
3 min readJul 20, 2019

Polymorphism tells us using the generic and basic class instead of an actual instance, and sometime we will implement default behavior in this basic class. It’s not a problem to test the default methods of this basic class if we can create its instance.

However, we used to define this basic class as abstract (to avoid someone create its instance or we need its children to override some abstract methods.)

public abstract class ViewModel {public abstract Data loadProduct();

public UIModel generateUIModel(Data data) {
// some complicated transformation
UIModel uiModel = new UIModel();
uiModel.title = data.name;
...
}
}

Thanks Mockito, it gave us the solution to test these public methods, which have concrete implementations of those abstract classes.

And thanks my colleague, Samuel Huang, shared this solution for me to improve our unit test quality.

Using Mockito

The solution Mockito offers is the mock with Answers’ attribute: CALLS_REAL_METHODS.

@Mock(answer = Answers.CALLS_REAL_METHODS)
private lateinit var viewModel: ViewModel
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
}

The attribute CALLS_REAL_METHODS, optional Answer to be used with mock(Class, Answer), will create an uninitialized and partial mock object, no constructors are run and no fields are set. This partial mock object will call the real method by default because all unstubbed methods will delegate to the real implementation.

In our unit test, we can verify default implementation of the abstract ViewModel:

@Mock(answer = Answers.CALLS_REAL_METHODS)
private lateinit var viewModel: ViewModel
@Test
fun testGenerateUIModel() {
// Arrange
val fakeData = Data().apply {
// test configuration
...
}
// Act
val uiModel = viewModel.generateUIModel(fakeData)

// Assert
....
}

The difference between Spy and Mock with CALLS_REAL_METHODS

There is a good explanation on StackOverflow:

The former CALLS_REAL_METHODS style creates an uninitialized object; no constructors are run and no fields are set. Generally this syntax is unsafe, as real implementations will interact with uninitialized fields that may constitute an invalid or impossible state.

The latter @Spy style allows you to call a constructor of your choice, or Mockito will try to call a no-arg constructor if the field is uninitialized. The fields are then copied into a generated Spy (that extends the spied-on type), allowing for much safer and more-realistic interactions.

- Jeff Bowman

In the above case, it’s fine for us to use Spy and the unit test still works.

@Spy
private lateinit var viewModel: ViewModel

We prefer to use Mock with CALLS_REAL_METHODS because we don’t need a realistic instance of the abstract class, we test its default methods ‘ implementations.

The public method that accesses private instance variables

However, how to do if the public method accesses private instance variables? Especially when this private variable will affect our test result.

public abstract class ViewModel {private Data data;public abstract Data loadProduct();

public UIModel generateUIModel() {
// some complicated transformation
UIModel uiModel = new UIModel();
uiModel.title = data.name;
....
}
}

We can’t use the above solution because CALLS_REAL_METHODS creates a partial mock object, and the data is uninitialized. So we need to use PowerMockito,

@Test
fun testGenerateUIModel() {
// Arrange
val fakeData = Data().apply {
// test configuration
...
}
val viewModel = PowerMockito.mock(ViewModel::class.java)
PowerMockito.doCallRealMethod()
.`when`(viewModel).generateUIModel()
Whitebox.setInternalState(viewModel, "data", fakeData);// Act
val uiModel = viewModel.generateUIModel()

// Assert
....
}

If you are interested in this Mock topic, I recommend reading How to test abstract class in Java and Testing an Abstract Class With JUnit.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Bram Yeh
Bram Yeh

Written by Bram Yeh

Lead Android & iOS Mobile Engineer at Yahoo (Verizon Media) Taiwan https://www.linkedin.com/in/hanruyeh/

No responses yet

Write a response