lastminute.com logo

Technology

The power of functional interfaces in test

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.


Read next

SwiftUI and the Text concatenations super powers

SwiftUI and the Text concatenations super powers

fabrizio_duroni
fabrizio duroni
marco_de_lucchi
marco de lucchi

Do you need a way to compose beautiful text with images and custom font like you are used with Attributed String. The Text component has everything we need to create some sort of 'attributed text' directly in SwiftUI. Let's go!!! [...]

A Monorepo Experiment: reuniting a JVM-based codebase

A Monorepo Experiment: reuniting a JVM-based codebase

luigi_noto
luigi noto

Continuing the Monorepo exploration series, we’ll see in action a real-life example of a monorepo for JVM-based languages, implemented with Maven, that runs in continuous integration. The experiment of reuniting a codebase of ~700K lines of code from many projects and shared libraries, into a single repository. [...]