Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4536

Mockito’s Java 8 Features

$
0
0

1. Overview

Java 8 introduced a range of new, awesome features, like lambda and streams. And naturally Mockito leveraged these recent innovations in its 2nd major version.

In this article, we are going to explore everything this powerful combination has to offer.

2. Mocking Interface With a Default Method

From Java 8 onwards we can now write method implementations in our interfaces. This might be a great new functionality, but its introduction to the language violated a strong concept that was part of Java since its conception.

Mockito version 1 was not ready for this change. Basically, because it didn’t allow us to ask it to call real methods from interfaces.

Imagine that we have an interface with 2 method declarations: the first one is the old-fashioned method signature we’re all used to, and the other is a brand new default method:

public interface JobService {
 
    Optional<JobPosition> findCurrentJobPosition(Person person);
    
    default boolean assignJobPosition(Person person, JobPosition jobPosition) {
        if(!findCurrentJobPosition(person).isPresent()) {
            person.setCurrentJobPosition(jobPosition);
            
            return true;
        } else {
            return false;
        }
    }
}

Notice that the assignJobPosition() default method has a call to the unimplemented findCurrentJobPosition() method.

Now, suppose we want to test our implementation of assignJobPosition() without writing an actual implementation of findCurrentJobPosition(). We could simply create a mocked version of JobService, then tell Mockito to return a known value from the call to our unimplemented method and call the real method when assignJobPosition() is called:

public class JobServiceUnitTest {
 
    @Mock
    private JobService jobService;

    @Test
    public void givenDefaultMethod_whenCallRealMethod_thenNoExceptionIsRaised() {
        Person person = new Person();

        when(jobService.findCurrentJobPosition(person))
              .thenReturn(Optional.of(new JobPosition()));

        doCallRealMethod().when(jobService)
          .assignJobPosition(
            Mockito.any(Person.class), 
            Mockito.any(JobPosition.class)
        );

        assertFalse(jobService.assignJobPosition(person, new JobPosition()));
    }
}

This is perfectly reasonable and it would work just fine given we were using an abstract class instead of an interface.

However, the inner workings of Mockito 1 were just not ready for this structure. If we were to run this code with Mockito pre version 2 we would get this nicely described error:

org.mockito.exceptions.base.MockitoException:
Cannot call real method on java interface. Interface does not have any implementation!
Calling real methods is only possible when mocking concrete classes.

Mockito is doing its job and telling us it can’t call real methods on interfaces since this operation was unthinkable before Java 8.

The good news is that just by changing the version of Mockito we’re using we can make this error go away. Using Maven, for example, we could use version 2.7.5 (the latest Mockito version can be found here):

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.7.5</version>
    <scope>test</scope>
</dependency>

There is no need to make any changes to the code. The next time we run our test, the error will no longer occur.

3. Return Default Values for Optional and Stream

Optional and Stream are other Java 8 new additions. One similarity between the two classes is that both have a special type of value that represent an empty object. This empty object makes it easier to avoid the so far omnipresent NullPointerException.

3.1. Example with Optional

Consider a service that injects the JobService described in the previous section and has a method that calls JobService#findCurrentJobPosition():

public class UnemploymentServiceImpl implements UnemploymentService {
 
    private JobService jobService;
    
    public UnemploymentServiceImpl(JobService jobService) {
        this.jobService = jobService;
    }

    @Override
    public boolean personIsEntitledToUnemploymentSupport(Person person) {
        Optional<JobPosition> optional = jobService.findCurrentJobPosition(person);
        
        return !optional.isPresent();
    }
}

Now, assume we want to create a test to check that, when a person has no current job position, they are entitled to the unemployment support.

In that case, we would force findCurrentJobPosition() to return an empty Optional. Before Mockito 2, we were required to mock the call to that method:

public class UnemploymentServiceImplUnitTest {
 
    @Mock
    private JobService jobService;

    @InjectMocks
    private UnemploymentServiceImpl unemploymentService;

    @Test
    public void givenReturnIsOfTypeOptional_whenMocked_thenValueIsEmpty() {
        Person person = new Person();

        when(jobService.findCurrentJobPosition(any(Person.class)))
          .thenReturn(Optional.empty());
        
        assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(person));
    }
}

This when(…).thenReturn(…) instruction on line 13 is necessary because Mockito’s default return value for any method calls to a mocked object is null. Version 2 changed that behavior.

Since we rarely handle null values when dealing with Optional, Mockito now returns an empty Optional by default. That is the exact same value as the return of a call to Optional.empty().

So, when using Mockito version 2, we could get rid of line 13 and our test would still be successful:

public class UnemploymentServiceImplUnitTest {
 
    @Test
    public void givenReturnIsOptional_whenDefaultValueIsReturned_thenValueIsEmpty() {
        Person person = new Person();
 
        assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(person));
    }
}

3.2. Example with Stream

The same behavior occurs when we mock a method that returns a Stream.

Let’s add a new method to our JobService interface that returns a Stream representing all the job positions that a person has ever worked at:

public interface JobService {
    Stream<JobPosition> listJobs(Person person);
}

This method is used on another new method that will query if a person has ever worked on a job that matches a given search string:

public class UnemploymentServiceImpl implements UnemploymentService {
   
    @Override
    public Optional<JobPosition> searchJob(Person person, String searchString) {
        return jobService.listJobs(person)
          .filter((j) -> j.getTitle().contains(searchString))
          .findFirst();
    }
}

So, assume we want to properly test the implementation of searchJob(), without having to worry about writing the listJobs() and assume we want to test the scenario when the person hasn’t work at any jobs yet. In that case, we would want listJobs() to return an empty Stream.

Before Mockito 2, we would need to mock the call to listJobs() to write such test:

public class UnemploymentServiceImplUnitTest {
 
    @Test
    public void givenReturnIsOfTypeStream_whenMocked_thenValueIsEmpty() {
        Person person = new Person();
        when(jobService.listJobs(any(Person.class))).thenReturn(Stream.empty());
        
        assertFalse(unemploymentService.searchJob(person, "").isPresent());
    }
}

If we upgrade to version 2, we could drop the when(…).thenReturn(…) call, because now Mockito will return an empty Stream on mocked methods by default:

public class UnemploymentServiceImplUnitTest {
 
    @Test
    public void givenReturnIsStream_whenDefaultValueIsReturned_thenValueIsEmpty() {
        Person person = new Person();
        
        assertFalse(unemploymentService.searchJob(person, "").isPresent());
    }
}

4. Leveraging Lambda Expressions

With Java 8’s lambda expressions we can make statements much more compact and easier to read. When working with Mockito, 2 very nice examples of the simplicity brought in by lambda expressions are ArgumentMatchers and custom Answers.

4.1. Combination of Lambda and ArgumentMatcher

Before Java 8, we needed to create a class that implemented ArgumentMatcher, and write our custom rule in the matches() method.

With Java 8, we can replace the inner class with a simple lambda expression:

public class ArgumentMatcherWithLambdaUnitTest {
 
    @Test
    public void whenPersonWithJob_thenIsNotEntitled() {
        Person peter = new Person("Peter");
        Person linda = new Person("Linda");
        
        JobPosition teacher = new JobPosition("Teacher");

        when(jobService.findCurrentJobPosition(
          ArgumentMatchers.argThat(p -> p.getName().equals("Peter"))))
          .thenReturn(Optional.of(teacher));
        
        assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(linda));
        assertFalse(unemploymentService.personIsEntitledToUnemploymentSupport(peter));
    }
}

4.2. Combination of Lambda and Custom Answer

The same effect can be achieved when combining lambda expressions with Mockito’s Answer.

For example, if we wanted to simulate calls to the listJobs() method in order to make it return a Stream containing a single JobPosition if the Person‘s name is “Peter”, and an empty Stream otherwise, we would have to create a class (anonymous or inner) that implemented the Answer interface.

Again, the use of a lambda expression, allow us to write all the mock behavior inline:

public class CustomAnswerWithLambdaUnitTest {
 
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);

        when(jobService.listJobs(any(Person.class))).then((i) ->
          Stream.of(new JobPosition("Teacher"))
          .filter(p -> ((Person) i.getArgument(0)).getName().equals("Peter")));
    }
}

Notice that, in the implementation above, there is no need for the PersonAnswer inner class.

5. Conclusion

In this article, we covered how to leverage new Java 8 and Mockito 2 features together to write cleaner, simpler and shorter code. If you are not familiar with some of the Java 8 features we saw here, check some of our articles:

Also, check the accompanying code on our GitHub repository.


Viewing all articles
Browse latest Browse all 4536

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>