Mockito in maven using JPMS cannot access a member of class with modifiers "private"

huangapple go评论76阅读模式
英文:

Mockito in maven using JPMS cannot access a member of class with modifiers "private"

问题

我正在将一个代码库迁移到Java 11和JPMS / Jigsaw,并在模拟方面遇到一些问题。

这是我尝试运行的测试。

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class DbTest {

    @Mock
    private Connection connection;

    @Mock
    private PreparedStatement preparedStatement;

    @Captor
    private ArgumentCaptor<Timestamp> dateCaptor;

    @Test
    public void setTimestamp_instant() throws SQLException {
        Instant inputTime = Instant.parse("2018-03-12T10:25:37.386Z");
        when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
        PreparedStatement preparedStatement = connection.prepareStatement("UPDATE fakeTable SET time = ? WHERE TRUE");
        RowPack rowPack = new RowPack(preparedStatement, DatabaseType.MYSQL);
        rowPack.setTimestamp(inputTime);
        verify(preparedStatement).setTimestamp(anyInt(), dateCaptor.capture(), Mockito.any(Calendar.class));
    }
}

在Eclipse中运行此测试会通过,但通过maven运行时失败,因为mockito在使用反射时无法找到某些资源。

org.mockito.exceptions.base.MockitoException: Problems setting field connection annotated with @org.mockito.Mock(name="", stubOnly=false, extraInterfaces={}, answer=RETURNS_DEFAULTS, serializable=false, lenient=false)
Caused by: java.lang.IllegalAccessException: class org.mockito.internal.util.reflection.ReflectionMemberAccessor cannot access a member of class foo.bar.DbTest (in module foo.bar) with modifiers "private"

我使用的是Surefire 3.0.0-M5、junit 5.7.0和mockito 3.5.10。

    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>

不用说,在切换到使用JPMS进行模块化之前,这在maven中工作得很好。

我已阅读了模块化世界中的测试,并尝试使用junit-platform-maven-plugin来替代surefire,但在使用mockito时遇到了类似的问题。

非常感谢您的帮助。

英文:

I am migrating a codebase to Java 11 and JPMS / Jigsaw and am having some trouble with mocking.

This is the test I am trying to run.

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class DbTest {

	@Mock
	private Connection connection;

	@Mock
	private PreparedStatement preparedStatement;

	@Captor
	private ArgumentCaptor&lt;Timestamp&gt; dateCaptor;

	@Test
	public void setTimestamp_instant() throws SQLException {
		Instant inputTime = Instant.parse(&quot;2018-03-12T10:25:37.386Z&quot;);
		when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
		PreparedStatement preparedStatement = connection.prepareStatement(&quot;UPDATE fakeTable SET time = ? WHERE TRUE&quot;);
		RowPack rowPack = new RowPack(preparedStatement, DatabaseType.MYSQL);
		rowPack.setTimestamp(inputTime);
		verify(preparedStatement).setTimestamp(anyInt(), dateCaptor.capture(), Mockito.any(Calendar.class));
	}
}

When running this test in Eclipse it passes but when I run it through maven it fails due to mockito being unable to find some resources using reflection.

org.mockito.exceptions.base.MockitoException: Problems setting field connection annotated with @org.mockito.Mock(name=&quot;&quot;, stubOnly=false, extraInterfaces={}, answer=RETURNS_DEFAULTS, serializable=false, lenient=false)
Caused by: java.lang.IllegalAccessException: class org.mockito.internal.util.reflection.ReflectionMemberAccessor cannot access a member of class foo.bar.DbTest (in module foo.bar) with modifiers &quot;private&quot;

I am using Surefire 3.0.0-M5, junit 5.7.0 and mockito 3.5.10.

		&lt;dependency&gt;
			&lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
			&lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
			&lt;scope&gt;test&lt;/scope&gt;
		&lt;/dependency&gt;
		&lt;dependency&gt;
			&lt;groupId&gt;org.mockito&lt;/groupId&gt;
			&lt;artifactId&gt;mockito-junit-jupiter&lt;/artifactId&gt;
			&lt;scope&gt;test&lt;/scope&gt;
		&lt;/dependency&gt;

Needless to say this worked well in maven before switching to modularising with JPMS.

I have read Testing in the modular world and tried the junit-platform-maven-plugin as a replacement for surefire but ran into similar problems with mockito.

Help would be greatly appreciated.

答案1

得分: 1

TL;DR — 你需要配置Surefire插件,以便在运行测试时向*java*传递--add-opens选项…

>> ### --add-opens
>> 如果您必须允许类路径上的代码进行<b>深度反射</b>以访问非公共成员,则请使用--add-opens运行时选项。
>>
>> 一些库进行深度反射,即setAccessible(true),以便访问所有成员,包括私有成员。您可以通过java命令行上的--add-opens选项授予此访问权限…


虽然我无法百分之百地复现您问题中的错误消息,但我成功生成了一个几乎相同的错误消息。它与您的问题相似,因此我有信心,我们两者的根本原因(以及解决方案)是相同的。

在这个您可以下载和构建的示例中,我解决了我遇到的错误…

&lt;plugin&gt;
	&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
	&lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
	&lt;version&gt;3.0.0-M5&lt;/version&gt;
	&lt;configuration&gt;
		&lt;argLine&gt;add-opens foo.bar/foo.bar=ALL-UNNAMED&lt;/argLine&gt;
	&lt;/configuration&gt;
&lt;/plugin&gt;

下载并构建示例。您可以随意根据需要进行修改。

英文:

TL;DR — You need to configure the Surefire Plugin to pass the --add-opens option to java when it runs your test…

>> ### --add-opens
>> If you have to allow code on the class path to do <b>deep reflection</b> to access nonpublic members, then use the --add-opens runtime option.
>>
>> Some libraries do deep reflection, meaning setAccessible(true), so they can access all members, including private ones. You can grant this access using the --add-opens option on the java command line…


Although I wasn't able to reproduce 100% verbatim the error message in your question, I was able to produce one that was pretty much the same. It was similar enough to yours, that I'm confident that the root cause (and the solution) of both mine and yours is the same.

In this demo that you can download and build, I resolved the error I got with…

&lt;plugin&gt;
	&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
	&lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
	&lt;version&gt;3.0.0-M5&lt;/version&gt;
	&lt;configuration&gt;
		&lt;argLine&gt;add-opens foo.bar/foo.bar=ALL-UNNAMED&lt;/argLine&gt;
	&lt;/configuration&gt;
&lt;/plugin&gt;

Download and build the demo. Feel free to modify it however you see fit.

答案2

得分: 1

另一种解决方案是使用以下配置来配置maven-surefire-plugin:<useModulePath>false</useModulePath>,这将停止强制执行模块化访问控制。

注意:这会使得你的测试在类路径上运行。通常,你希望测试在尽可能接近运行时环境的情况下运行。

英文:

Another solution is to configure the maven-surefire-plugin with &lt;useModulePath&gt;false&lt;/useModulePath&gt;, which will stop enforcing modular access control.

Caution: This will make your tests run on the classpath. Usually, you want tests to run in as similar an environment as possible to that at runtime.

答案3

得分: 0

我的解决方案是在测试源代码(Maven中的src/test/java)中放置一个独立的module-info.java,并在测试模块中指定open(参见允许仅在运行时访问模块中的所有包)。内容如下:

// "open" 似乎是一个魔法词:它开放了反射访问权限
// 必须使用与主模块相同的模块名称,以便主模块也具有名称 "com.foo.bar"
open module com.foo.bar {
    // 我仍在使用 junit4
    requires junit;
    // 在这里添加 Mockito 的依赖
    requires org.mockito;
    // 非常重要,Mockito 需要它
    requires net.bytebuddy;
    // 在这里添加你的依赖
    requires org.bouncycastle.provider;
}
英文:

My solution was to place an own module-info.java inside the test sources (src/test/java in Maven) specifying open (See Allowing runtime-only access to all packages in a module) for the test module with the following contents:

// &quot;open&quot; seems to be the magic word: it opens up for reflective access
// the same module name like for the main module must be used, so the main module has also the name &quot;com.foo.bar&quot;
open module com.foo.bar {
// I use still juni4
    requires junit;
// require Mockito here
    requires org.mockito;
// very important, Mockito needs it
    requires net.bytebuddy;
// add here your stuff
    requires org.bouncycastle.provider;
}

答案4

得分: 0

我遇到了类似的问题。我正在一个使用Java 11 jpms项目上使用Junit和Mockito,并且希望使用Maven运行我的测试。

在借鉴deduper的很棒答案的基础上,我添加了:

<configuration>
    <argLine>--add-opens foo.bar/foo.bar=ALL-UNNAMED</argLine>
</configuration>

在我的surefire配置中。

请注意这两个前缀破折号 --,没有它们,add-opens会被解析为一个类并抛出错误。

英文:

I had a similar problem. I am using Junit and Mockito on a Java 11 jpms project and wanted to run my tests using maven.

To build upon deduper's great answer, I added:

&lt;configuration&gt;
    &lt;argLine&gt;--add-opens foo.bar/foo.bar=ALL-UNNAMED&lt;/argLine&gt;
&lt;/configuration&gt;

on my surefire configuration.

Notice the 2 prefixed dashes --, without them add-opens was parsed as a class and throws an Error.

huangapple
  • 本文由 发表于 2020年9月29日 21:30:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/64120676.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定