More compact Mockito with Java 8, lambda expressions and Mockito-Java8 add-ons
Mockito-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; }
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
Reference: | More compact Mockito with Java 8, lambda expressions and Mockito-Java8 add-ons from our JCG partner Marcin Zajaczkowski at the Solid Soft blog. |