Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating Stubs Using Java 8 Lambdas

DZone's Guide to

Creating Stubs Using Java 8 Lambdas

Assuming your project isn't overly complex, here is how you can ditch mocking frameworks and create your own stubs using lambda expressions.

· Java Zone ·
Free Resource

FlexNet Code Aware, a free scan tool for developers. Scan Java, NuGet, and NPM packages for open source security and open source license compliance issues.

Using stub (or mock) objects and mock frameworks is a common approach for writing tests, but people starting to use them very rarely consider the difficulties they are going to face in a dynamic project where requirements change very often, and when the tests have to be maintained over time by other people in the team.

I recently wrote an article giving some insights on good and bad practices using mocks with the help of Java examples. In general, we often rely on libraries for the creation of stub (or mock) objects in Java. This article shows situations where the dependencies to such libraries can be omitted by using Java 8 lambda expressions. The code is based on the examples from my previous article, which makes the comparison of various approaches easier.

The application used for this demonstration represents a standard use case where an internal service loads data from an external one and uses it for its own purposes. The external service is hidden behind an API containing a single interface. Our goal is to test the internal service functionality isolated from the details of the external one. The code can be found in my GitHub repository. Here is one of the tests based on stub creation implemented using Mockito.

public class InternalDataServiceStubTest {

    private InternalDataService internalDataService;

    @Mock
    private ExternalDataService mockDataService;
    private DomainObjectBuilder domainObjectBuilder = new DomainObjectBuilderImpl();

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        internalDataService = new InternalDataServiceImpl(mockDataService, domainObjectBuilder);
    }

    @Test
    public void testLoadData() {
        List <String> ids = Arrays.asList("42", "1984");

        //expectations
        when(mockDataService.loadData(any())).thenReturn(Arrays.asList("42:Douglas Adams", "1984:George Orwell"));

        //Exercise
        List <DomainObject> loadedObjects = internalDataService.loadDomainObjects(ids);

        //Verify state
        List <DomainObject> expectedResult = Arrays.asList(new DomainObjectImpl("42", "Douglas Adams"), new DomainObjectImpl("1984", "George Orwell"));
        Assert.assertEquals(expectedResult, loadedObjects);
    }
}


The external service is mocked via the @Mock annotation, and a corresponding expectation is defined as part of the test. Now assume we would like to omit the dependency to a mocking framework. We could easily implement the stub object using a simple Java class implementing the ExternalDataService interface, even using an anonymous class. And when it comes to anonymous classes, why not use a lambda expression instead?

public class InternalDataServiceLambdaTest {

    private InternalDataService internalDataService;
    private DomainObjectBuilder domainObjectBuilder = new DomainObjectBuilderImpl();

    @Before
    public void setUp() {
        internalDataService = new InternalDataServiceImpl(ids -> Arrays.asList("42:Douglas Adams", "1984:George Orwell"), domainObjectBuilder);
    }

    @Test
    public void testLoadData() {
        List <String> ids = Arrays.asList("42", "1984");

        //Exercise
        List <DomainObject> loadedObjects = internalDataService.loadDomainObjects(ids);

        //Verify state
        List <DomainObject> expectedResult = Arrays.asList(new DomainObjectImpl("42", "Douglas Adams"), new DomainObjectImpl("1984", "George Orwell"));
        Assert.assertEquals(expectedResult, loadedObjects);
    }
}


We removed the dependency to Mockito and made the code simpler, as we also removed the expectations part of the test. We were able to do that because the external interface we are calling is a single method interface, and Java 8 treats lambdas as an instance of an interface type. It formalizes this into functional interfaces. A functional interface is just an interface with a single method. All the existing single method interfaces, like Runnable and Comparator in the JDK, are now functional interfaces, and lambdas can be used anywhere a single abstract method interface is used. Furthermore, we can send lambda expressions or method references where functional interfaces are expected.

In the example above, the lambda expression can be easily replaced by a method reference as follows:

public class InternalDataServiceLambdaTest {
private InternalDataService internalDataService;
private DomainObjectBuilder domainObjectBuilder = new DomainObjectBuilderImpl();

@Before
public void setUp() {
internalDataService = new InternalDataServiceImpl(this::callStubExternalService, domainObjectBuilder);
}

@Test
public void testLoadData() {
List<String> ids = Arrays.asList("42", "1984");

//Exercise
List<DomainObject> loadedObjects = internalDataService.loadDomainObjects(ids);

//Verify state
List<DomainObject> expectedResult = Arrays.asList(new DomainObjectImpl("42", "Douglas Adams"), new DomainObjectImpl("1984", "George Orwell"));
Assert.assertEquals(expectedResult, loadedObjects);
}

private List<String> callStubExternalService(Collection<String> ids) {
return Arrays.asList("42:Douglas Adams", "1984:George Orwell");
}
}


An additional method — private List<String> callStubExternalService(Collection<String> ids) — is added to the test. It is passed as a method reference to the constructor of the InternalDataService. This approach is preferable in cases where the lambda expression is longer than a row and decreases the readability of the code.

To summarize, we easily stubbed away the dependency to the external service, which helped to rapidly develop and test our code without making the code dependent to an external mocking library. This was possible because we basically depend on a functional interface. In more complex situations, where our code is tightly coupled with an external API, this might not be possible.

 Scan Java, NuGet, and NPM packages for open source security and license compliance issues. 

Topics:
java ,lambda expression ,mock objects ,java 8 ,stub ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}