【问题标题】:Dependency issue: Java SE-1.8, JUnit 5, Mockito 4.0 and PowerMock依赖性问题:Java SE-1.8、JUnit 5、Mockito 4.0 和 PowerMock
【发布时间】:2022-01-02 19:45:33
【问题描述】:

在一个带有 Maven 的项目(Eclipse IDE 4.12)中,我成功地使用了 JUnit 5 和 Mockito 4.0.0 进行测试/模拟。现在,为了测试受保护的方法,我也想使用 PowerMock。

在网上搜索了一段时间后,我最终得到了这个 pom.xml:

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>sdi</groupId>
<artifactId>id4mqtt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Id4Mqtt</name>
<description>Rapid IoT development framework</description>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
</properties>

<repositories>
    <repository>
        <id>Eclipse Paho Repo</id>
        <url>%REPOURL%</url>
    </repository>
</repositories>

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-inline</artifactId>
        <version>4.0.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.apache.geronimo.gshell.support/gshell-io -->
    <dependency>
        <groupId>org.apache.geronimo.gshell.support</groupId>
        <artifactId>gshell-io</artifactId>
        <version>1.0-alpha-2</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.powermock/powermock-mockito-release-full -->
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-mockito-release-full</artifactId>
        <version>1.6.4</version>
        <type>pom</type>
        <scope>test</scope>
    </dependency>

</dependencies>

要测试的候选人是(删除不感兴趣的代码)

package applicationBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;


public class ConfigurationBuilder extends BuilderBase {

    private Properties properties;
    private String[] uriList;
     
    public ConfigurationBuilder(final Properties aProperties) {
        properties = aProperties;
    } // ConfigurationBuilder(...)
    
    public Object build(final String aBrokerName) {

        List<String> brokerList = grepPropertyKey("[^\\.]+\\.CommunicationMode");
        List<MQTTClientDao> clientDaoList = new ArrayList<>();

        for(String broker : brokerList) {
            clientDaoList.add(buildConfig(broker));
        } // rof(broker)
        
        return clientDaoList;
        
    } // build()

    protected MQTTClientDao buildConfig(String broker) {
        
        buildUriList(broker);
        
        return new MQTTClientDao(mqttClient, connectOptions);
        
    } // buildConfig

    protected void buildUriList(final String aBroker) {
        
        ServerUriBuilder builder = new ServerUriBuilder(properties);
        uriList = (String[]) builder.build(aBroker);
        
    } // buildUriList()

} // class

应该测试 ServerUriBuilder.build(properties) 方法的正确调用的测试类如下所示:

package test.applicationBuilder;

import static org.powermock.api.mockito.PowerMockito.*;

import java.util.Properties;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.legacy.PowerMockRunner;

import applicationBuilder.ConfigurationBuilder;
import applicationBuilder.ServerUriBuilder;

@RunWith(PowerMockRunner.class)
@PrepareForTest(ServerUriBuilder.class)
class ConfigurationBuilderTest {

    private static Properties properties;
    private static final String BROKER_NAME = "Broker1";
    
    
    @BeforeEach
    void setUp() throws Exception {
        
        properties = new Properties();
        
    }

    @AfterEach
    void tearDown() throws Exception {
        
        properties = null;
        
    }

    @Test
    final void testBuildUriList() {
        
        ConfigurationBuilder cut = spy(new ConfigurationBuilder(properties));
        
        try {
            
            verifyPrivate(cut).invoke("buildUriList", BROKER_NAME);
            
        } catch (Exception e) { e.printStackTrace(); }
        
    } // testBuildUriList()

    
} // class

一切似乎都很好,因为 Eclipse 没有报告任何错误。但是当我运行测试时会抛出异常:

java.lang.NoClassDefFoundError: org/mockito/cglib/proxy/MethodInterceptor
    at org.powermock.api.mockito.PowerMockito.spy(PowerMockito.java:220)
    at test.applicationBuilder.ConfigurationBuilderTest.testBuildUriList(ConfigurationBuilderTest.java:130)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:628)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:117)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:184)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:180)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:137)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: java.lang.ClassNotFoundException: org.mockito.cglib.proxy.MethodInterceptor
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 55 more


我的问题是:

  1. 我可以使用此组合进行测试吗?
  2. 我的 pom.xml 是否正确并包含所有必需的模块?
  3. 如何进行测试?

提前致谢!

【问题讨论】:

  • Powermockito 是基于 JUnit 4 的 @RunWith(PowerMockRunner.class) 这表明......这不适用于 JUnit Jupiter(又名 Junit 5。)......另一个问题是:为什么需要 powermockito?为什么不使用 mockito 呢? buildUril.... 方法是受保护但不是私有的?...
  • 在使用junit5时最好和mockito-inline一起使用来代替powermock
  • @khmarbaise:这里stackoverflow.com/a/34093433/5564903 解释了如何测试受保护的方法。但是这三种解决方案都不是我可以接受的。既不喜欢将生产代码和测试代码混合在同一个包中,也不喜欢仅仅为了可测试性而更改生产代码。测试代码中的包装器类也不是一个选项,因为被测试的类有很多私有方法要测试......接下来是我有更多的构建器类要测试,它们都遵循相同的基本模式。使用包装器类,每个测试都有相同的丑陋且容易出错的包装器。
  • @Felipe:使用 mockito-inline 时测试会是什么样子?可以举个例子吗?

标签: java maven junit mockito powermock


【解决方案1】:

我猜您正在混合使用 Junit 版本,而 Powermock 仅与 Junit 4 兼容。虽然您可以将 JUnit 4 与 Junit 5 一起使用,但因为您使用的是 Powermock,您可能会遇到这个问题。例如,我看到人们使用

    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-testng</artifactId>
        <version>2.0.2</version>
        <scope>test</scope>
    </dependency>

但我建议使用 mvn dependency:tree 检查依赖关系,不要将 JUnit 5 与 Powermock 混合使用。


将 JUnit 5 与 mockito 结合使用

如果您想使用 JUnit5 模拟静态方法,请添加与 Powermockito 执行相同工作的 Mockito-inline 依赖项。

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.1.0</version>
    <scope>test</scope>
</dependency>

对于没有参数的静态方法,您可以使用 Java 8 的 :: lambda。我喜欢在一行中使用 try/catch,因为您不必在完成测试后调用 utilities.close(); .

@Test
void givenStaticMethodWithNoArgs_whenMocked_thenReturnsMockSuccessfully() {
    assertThat(StaticUtils.name()).isEqualTo("string-to-mock");

    try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
        utilities.when(StaticUtils::name).thenReturn("value-name");
        assertThat(StaticUtils.name()).isEqualTo("value-name");
    }
    assertThat(StaticUtils.name()).isEqualTo("string-to-mock");
}

或者对于需要参数的方法

@Test
void givenStaticMethodWithArgs_whenMocked_thenReturnsMockSuccessfully() {
    assertThat(StaticUtils.range(2, 6)).containsExactly(2, 3, 4, 5);

    try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
        utilities.when(() -> StaticUtils.range(2, 6))
          .thenReturn(Arrays.asList(10, 11, 12));

        assertThat(StaticUtils.range(2, 6)).containsExactly(10, 11, 12);
    }

    assertThat(StaticUtils.range(2, 6)).containsExactly(2, 3, 4, 5);
}

运行示例可以在here找到。

【讨论】:

  • 真的需要 mockStatic 因为方法 buildUriList 是受保护的,但不是私有的也不是静态的?
  • @Felipe:感谢您的建议,但我很抱歉说它不起作用。方法 buildUriList() 不是公开的,因此 Eclipse 会发出“ConfigurationBuilder 类型的方法 buildUriList(String) 不可见”。而且它不是静态方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-29
  • 1970-01-01
  • 2021-04-05
  • 2020-08-02
  • 1970-01-01
  • 2020-04-15
  • 2020-02-01
相关资源
最近更新 更多