Technology Blog

The power of functional interfaces in test

March 02, 2021 - 3 min read ⏱️

manuel_mandatori
manuel mandatori

How to use functional interfaces to mock behaviours in tests without implementing classes


Hello, everyone!

In this article, you will find part of the journey of a TDD enthusiast that likes to push the test to the next level. From the very beginning, every time I wrote a test in Java with a TDD approach, I usually import some mocking libraries some mock libraries such as Mockito or JMock. I use them to mock behaviors in collaborators of the class under test with the library syntax and reflecting very often the production code. Let’s see an example with Mockito framework about the mocking of collaborators where a class will call two repositories to retrieve some data. In this example we have a simple RetrieveProductsUseCase that needs hotel and flights (to stay in the same business) from HotelsRepository and FlightsRepository:

import org.junit.Before;
import org.junit.Test;

import java.util.List;

import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class SimpleTest {

    private FlightsRepository flightsRepository = mock(FlightsRepository.class);
    private HotelsRepository hotelsRepository = mock(HotelsRepository.class);

    private RetrieveProductsUseCase underTest;

    @Before
    public void setUp() {
        underTest = new RetrieveProductsUseCase(flightsRepository, hotelsRepository);
    }

    @Test
    public void name() {
        long idBooking = 123456789L;
        when(flightsRepository.getFor(idBooking)).thenReturn(new Flight());
        when(hotelsRepository.getFor(idBooking)).thenReturn(new Hotel());
        List<Product> products = underTest.retrieveFor(123456789L);
        assertThat(products, hasSize(2));
    }

    private class RetrieveProductsUseCase {

        private FlightsRepository flightsRepository;
        private HotelsRepository hotelsRepository;

        public RetrieveProductsUseCase(
                FlightsRepository flightsRepository,
                HotelsRepository hotelsRepository) {

            this.flightsRepository = flightsRepository;
            this.hotelsRepository = hotelsRepository;
        }

        public List<Product> retrieveFor(long idBooking) {
            return asList(flightsRepository.getFor(idBooking), hotelsRepository.getFor(idBooking));
        }
    }

    private interface FlightsRepository {
        Flight getFor(long idBooking);
    }

    private interface HotelsRepository {
        Hotel getFor(long idBooking);
    }

    private class Product {}
    
    private class Hotel extends Product {}

    private class Flight extends Product {}

}

From the example above, the two repositories contain only one method to retrieve the products. Why not using the FunctionalInterface annotation? This handy annotation was introduced in Java 8 and defines an interface as a functional interface when it contains only one abstract method (Oracle reference: [Functional Interface] https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html). Thanks to this annotation we can substitute the implementation of the interface with a lambda expression:

public class SimpleTest {
    @Test
    public void name() {
        long idBooking = 123456789L;
        flightsRepository = () -> new Flight();
        hotelsRepository = () -> new Hotel();
        
        List<Product> products = underTest.retrieveFor(123456789L);
        assertThat(products, hasSize(2));
        
    }

    @FunctionalInterface
    private interface FlightsRepository {
        Flight getFor(long idBooking);
    }

    @FunctionalInterface
    private interface HotelsRepository {
        Hotel getFor(long idBooking);
    }

}

Now the test is more clear and there is no concern about the implementation of the two repositories. The usage of FunctionalInterface allows the developer to focus on testing the functionality without the constraint to create dummy implementations.

Another important feature of the mocking libraries is checking on the input parameters. How could we do the same inside the anonymous function? With assertions :D Let’s take the example and change it to be sure that the idBooking passed to the repository is the same that we use as input to the use case:

public class SimpleTest {
    @Test
    public void name() {
        long idBooking = 123456789L;
        flightsRepository = (inputIdBooking) -> {
            assertEquals(inputIdBooking, idBooking);
            return new Flight();
        };
        hotelsRepository = () -> {
            assertEquals(inputIdBooking, idBooking);
            return new Hotel();
        };
        
        List<Product> products = underTest.retrieveFor(123456789L);
        assertThat(products, hasSize(2));
        
    }

    @FunctionalInterface
    private interface FlightsRepository {
        Flight getFor(long idBooking);
    }

    @FunctionalInterface
    private interface HotelsRepository {
        Hotel getFor(long idBooking);
    }

}

Now we are sure that when the input value is not the wanted one, the test will fail in the same way it would with mocking libraries.

After these examples, we could have the last question: What is missing when you use FunctionalInterface instead of mocks? The response is: the number of calls verification, which must be done manually. The verify method of Mockito accepts a parameter that corresponds to the number of calls expected. With the lambda function, you should create a counter variable and increment it every time the mock is called.


lastminute.com folks
Written by lastminute.com folks who are busy living. You should follow us on Twitter

The postings on this site are authors' opinions and experiences and do not necessarily represent the postings, strategies or opinions of lastminute.com group.