使用 JPMS 的 Maven 中的 Mockito 无法使用修饰符 "private" 访问 class 的成员

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

我正在将代码库迁移到 Java 11 和 JPMS / Jigsaw,但在模拟方面遇到了一些麻烦。

这是我正在尝试的测试 运行。

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 中运行良好。

我已阅读 Testing in the modular world 并尝试使用 junit-platform-maven-plugin 作为 surefire 的替代品,但 运行 遇到与 mockito 类似的问题。

将不胜感激。

TL;DR — 您需要配置 Surefire 插件以将 --add-opens 选项传递给 java 当它运行你的测试时…

--add-opens

If you have to allow code on the class path to do deep reflection 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…


虽然我无法 100% 逐字重现您问题中的错误消息,但我能够生成一个几乎相同的错误消息。它与你的非常相似,我相信我和你的根本原因(和解决方案)是相同的。

In this demo that you can download and build,我解决了我遇到的错误...

…
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M5</version>
    <configuration>
        …
        <argLine>add-opens foo.bar/foo.bar=ALL-UNNAMED</argLine>
        …
    </configuration>
</plugin>
…

Download and build the demo。您可以随意修改它。

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

注意:这将使您的测试 运行 在类路径上。通常,您希望 运行 在与 运行 时尽可能相似的环境中进行测试。

我的解决方案是在测试源中放置一个自己的 module-info.java(Maven 中的 src/test/java),为测试模块指定 open(参见 Allowing runtime-only access to all packages in a module)以下内容:

// "open" 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 "com.foo.bar"
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;
}

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

为了建立 deduper 的出色答案,我添加了:

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

在我的 surefire 配置上。

注意 2 个带前缀的破折号 --,没有它们 add-opens 被解析为 class 并抛出错误。