Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map class is not getting spied properly with mockito-inline for Java 17 #2760

Closed
ankushthakur2593 opened this issue Sep 27, 2022 · 1 comment

Comments

@ankushthakur2593
Copy link

Version Information
Java Version - 17
Mockito Version - 4.0.0
Spring Boot Version - 2.6.10
System Info- MacOS#12.6#Apple M1 Pro

Issue Description
I'm working on the task of migrating my project to Java 17 and Spring boot 2.6.10.
In the existing test cases, we're spying a hashmap that has some values in it.
With Java 17 and mockito (4.0.0), mockito's spy is not working properly and is returning an empty hashmap.

So after looking at #2589 , I added mockito-inline as an extra dependency in the project.
Now, it's able to spy the hashmap with all the relevant values but it's adding an extra entry in the Map -
Key- MethodHandle(Object)int
value - int array

Exception Message-

java.lang.ClassCastException: class java.lang.invoke.BoundMethodHandle$Species_LI cannot be cast to class java.util.UUID (java.lang.invoke.BoundMethodHandle$Species_LI and java.util.UUID are in module java.base of loader 'bootstrap')

	at java.base/java.util.HashMap.forEach(HashMap.java:1421)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

I'm able to reproduce this issue in a small dummy project. Let me know If any extra information is required here.

NOTE - This same sample project and my application project work fine with the same configuration on Java 11.

Required Files of the sample project-
a) Domain-

import java.util.UUID;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class RandomFeed {
    private UUID id;
    private String subject;
}

b) Service layer-

@Service
public class RandomFeedService {

    public Map<UUID, List<RandomFeed>> groupFeedById(List<RandomFeed> newsFeedEntries) {
        Map<UUID, List<RandomFeed>> randomEntries = new HashMap<>();
        newsFeedEntries.forEach(randomFeedEntry -> {
            final UUID accountId = randomFeedEntry.getId();
            if (!randomEntries.containsKey(accountId)) {
                randomEntries.put(accountId, new ArrayList<>());
            }
            randomEntries.get(accountId).add(randomFeedEntry);
        });
        return randomEntries;
    }
}

c) Test file-

@SpringBootTest
class DemoApplicationTests {

    @InjectMocks
    private RandomFeedService randomFeedService;

    @Test
    void test() {
        List<RandomFeed> newsFeedEntries = fetchData();
        Map<UUID, List<RandomFeed>> newsEntries = spy(randomFeedService.groupFeedById(newsFeedEntries));
        newsEntries.forEach((k, v) -> {
           // It Fails here since one of the entry in this map is not of type - UUID.
        });
    }

    List<RandomFeed> fetchData() {
        return Arrays.asList(
            RandomFeed.builder().id(UUID.randomUUID()).subject("Instance 1").build(),
            RandomFeed.builder().id(UUID.randomUUID()).subject("Instance 2").build());
    }
}

d) pom file-

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.10</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.mocktest</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-inline</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
@TimvdLippe
Copy link
Contributor

Unfortunately, HashMap is one of the classes that Mockito relies on internally for its behavior. Stubbing HashMap will therefore lead to undefined behavior. Additionally, it is advised not to mock classes you don't own: https://github.com/mockito/mockito/wiki/How-to-write-good-tests#dont-mock-a-type-you-dont-own We are working on improving the user experience by working on a DoNotMock feature to avoid mocking classes/methods that are known to crash Mockito internals (#1833). Therefore, I am closing this as "Infeasible". Apologies for the uninformative exception that is thrown.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants