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

List Matchers with Generics in Mockito

$
0
0

1. Overview

When writing unit tests in Java using Mockito, we often need to stub methods that accept generic List parameters, such as List<String>, List<Integer>, etc. However, due to Java’s type erasure, handling these cases requires some extra consideration.

In this tutorial, we’ll explore how to use List matchers with generics in Mockito. We’ll cover both Java 7 and Java 8+ cases.

2. Introduction to the Problem

First, let’s understand the generics and Mockito challenge through an example.

Let’s say we have an interface with a default method:

interface MyInterface {
    default String extractFirstLetters(List<String> words) {
        return String.join(", ", words.stream().map(str -> str.substring(0, 1)).toList());
    }
}

The extractFirstLetters() method accepts a List of String values and returns a comma-separated String containing the first characters of each word in the List.

Now, we would like to mock MyInterface in our tests and stub the extractFirstLetters() method:

MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(any(List.class))).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<String>()));

In this example, we simply used ArgumentMatchers.any(List.class) to match the generic List parameter. If we run the test, it passes. Therefore, the stub works as expected.

However, if we check the compiler log, we see a warning:

Unchecked assignment: 'java.util.List' to 'java.util.List<java.lang.String>' 

This is because we used any(List.class) to match the generic List<String> parameter. The compiler cannot verify at compile time that a raw List contains only String elements.

Next, let’s explore the proper ways to stub a method and match generic List parameters. Since type inferences are handled differently in Java 7 and Java 8+, we’ll cover both Java 7 and Java 8+ cases.

3. Matching Generic List Parameters in Java 7

Sometimes, we must work with legacy Java projects with older Java versions.

In Java 7, type inference was limited, so the compiler struggled to determine the correct generic type when using ArugmentMatchers like anyList(). Therefore, we must specify the generic type when using Mockito’s ArgumentMatchers.

Next, let’s see how to stub the extractFirstLetters() method in Java 7:

// Java 7
MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(ArgumentMatchers.<String>anyList())).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<>()));

As the test shows, we specified <String> type on the anyList() matcher. The test compiles and passes.

Without explicitly specifying <String>, anyList() returns List<?>, which doesn’t match the expected List<String> and leads to compiler errors in Java 7.

4. Matching Generic List Parameters in Java 8+

The compiler became smarter in Java 8 or later versions. We don’t have to specify type arguments explicitly.

Therefore, we can simply use anyList() without specifying <String>, and the compiler correctly infers the expected type:

MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(anyList())).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<>()));

If we execute the test, it passes without compiler warnings. This is because the Java 8+ compiler automatically infers the generic type from extractFirstLetters(List<String>).

Some of us may come up with an approach using the any() matcher to match the required generic parameter:

MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(any())).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<>()));

The code looks compact and straightforward. Similarly, the Java 8+ compiler can infer the generic type from the target method, so this approach works as well. However, any() is a generic matcher that matches any object. It’s less type-specific and can lead to scenarios where the matcher is less precise.

In practice, for a method that takes a List<String> explicitly, using anyList() would be more precise and self-documenting, indicating clearly that the matcher expects a List. Therefore, although both matches can technically be used, anyList() is preferred for better type safety and readability when dealing with List parameters.

5. Conclusion

Mockito’s List matchers make it easy to work with generic List parameters, but we need to be aware of type erasure and Java’s type inference.

In this article, we’ve explored how to match generic List parameters properly:

  • In Java 7 – We must explicitly specify generic types: ArgumentMatchers.<T>anyList()
  • In Java 8 and later versions – We can directly use anyList() without explicitly specifying <T>, as the compiler can infer types automatically

Understanding these concepts allows us to write cleaner, more effective unit tests in legacy and modern Java projects.

As always, the complete source code for the examples is available over on GitHub.

The post List Matchers with Generics in Mockito first appeared on Baeldung.
       

Viewing all articles
Browse latest Browse all 3801

Latest Images

Trending Articles



Latest Images

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