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.
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.