lastminute.com logo

Technology

The power of functional interfaces in test

staff
staff

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


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.


staff

We are the European Travel Tech leaders in Dynamic Holiday Packages. We leverage our Technology to simplify, personalise, and enhance customers’ travel experience.


Read next

React Universe 2024

React Universe 2024

fabrizio_duroni
fabrizio duroni
sam_campisi
sam campisi

Let's dive into the talks from React Universe 2024 that stood out to us the most and share the key insights we gained. From innovative debugging tools to cross-platform development strategies, we’ll walk you through what we found valuable and how it’s shaping our approach to React and React Native development. [...]

Tech Radar As a Collaboration Tool

Tech Radar As a Collaboration Tool

rabbani_kajamohideen
rabbani kajamohideen

A tech radar is a visual and strategic tool used by organizations to assess and communicate the status and future direction of various technologies, frameworks, tools, and platforms. [...]