More Compact Mockito with Java 8 and Lambda Expressions
Join the DZone community and get the full member experience.
Join For FreeMockito-Java8 is a set of Mockito add-ons leveraging Java 8 and lambda expressions to make mocking with Mockito even more compact.
At the beginning of 2015 I gave my flash talk Java 8 brings power to testing! at GeeCON TDD 2015 and DevConf.cz 2015. In my speech using 4 examples I showed how Java 8 – namely lambda expressions – can simplify testing tools and testing in general. One of those tools was Mokcito. To not let my PoC code die on slides and to make it simply available for others I have released a small project with two, useful in specified case, Java 8 add-ons for Mockito.
Quick introduction
As a prerequisite, let's assume we have the following data structure:
@Immutable
class ShipSearchCriteria {
int minimumRange;
int numberOfPhasers;
}
and a class we want to stub/mock:
public class TacticalStation {
public int findNumberOfShipsInRangeByCriteria(
ShipSearchCriteria searchCriteria) { ... }
}
The library provides two add-ons:
Lambda matcher - allows to define matcher logic within a lambda expression.
given(ts.findNumberOfShipsInRangeByCriteria(
argLambda(sc -> sc.getMinimumRange() > 1000))).willReturn(4);
Argument Captor - Java 8 edition - allows to use `ArgumentCaptor` in a one line (here with AssertJ):
verify(ts).findNumberOfShipsInRangeByCriteria(
assertArg(sc -> assertThat(sc.getMinimumRange()).isLessThan(2000)));
Lambda matcher
With a help of the static method argLambda a lambda matcher instance is created which can be used to define matcher logic within a lambda expression (here for stubbing). It could be especially useful when working with complex classes pass as an argument.
@Test
public void shouldAllowToUseLambdaInStubbing() {
//given
given(ts.findNumberOfShipsInRangeByCriteria(
argLambda(sc -> sc.getMinimumRange() > 1000))).willReturn(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(1500, 2))).isEqualTo(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(700, 2))).isEqualTo(0);
}
In comparison the same logic implemented with a custom Answer in Java 7:
@Test
public void stubbingWithCustomAsnwerShouldBeLonger() { //old way
//given
given(ts.findNumberOfShipsInRangeByCriteria(any())).willAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
ShipSearchCriteria criteria = (ShipSearchCriteria) args[0];
if (criteria.getMinimumRange() > 1000) {
return 4;
} else {
return 0;
}
}
});
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(1500, 2))).isEqualTo(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(700, 2))).isEqualTo(0);
}
Even Java 8 and less readable constructions don't help too much:
@Test
public void stubbingWithCustomAsnwerShouldBeLongerEvenAsLambda() { //old way
//given
given(ts.findNumberOfShipsInRangeByCriteria(any())).willAnswer(invocation -> {
ShipSearchCriteria criteria = (ShipSearchCriteria) invocation.getArguments()[0];
return criteria.getMinimumRange() > 1000 ? 4 : 0;
});
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(1500, 2))).isEqualTo(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(700, 2))).isEqualTo(0);
}
Argument Captor - Java 8 edition
A static method assertArg creates an argument matcher which implementation internally uses ArgumentMatcher with an assertion provided in a lambda expression. The example below uses AssertJ to provide meaningful error message, but any assertions (like native from TestNG or JUnit) could be used (if really needed). This allows to have inlined ArgumentCaptor:
@Test
public void shouldAllowToUseAssertionInLambda() {
//when
ts.findNumberOfShipsInRangeByCriteria(searchCriteria);
//then
verify(ts).findNumberOfShipsInRangeByCriteria(
assertArg(sc -> assertThat(sc.getMinimumRange()).isLessThan(2000)));
}
In comparison to 3 lines in the classic way:
@Test
public void shouldAllowToUseArgumentCaptorInClassicWay() { //old way
//when
ts.findNumberOfShipsInRangeByCriteria(searchCriteria);
//then
ArgumentCaptor<ShipSearchCriteria> captor =
ArgumentCaptor.forClass(ShipSearchCriteria.class);
verify(ts).findNumberOfShipsInRangeByCriteria(captor.capture());
assertThat(captor.getValue().getMinimumRange()).isLessThan(2000);
}
Summary
The presented add-ons were created as PoC for my conference speech, but should be fully functional and potentially useful in the specific cases. To use it in your project it is enough to use Mockito 1.10.x or 2.0.x-beta, add `mockito-java8` as a dependency and of course compile your project with Java 8+.
More details are available on the project webpage: https://github.com/szpak/mockito-java8
Published at DZone with permission of Marcin Zajączkowski. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments