当前位置: 首页 > 知识库问答 >
问题:

Mockito在本地模拟最终类,但在Jenkins中失败

杜哲彦
2023-03-14

我已经为静态方法编写了一些单元测试。静态方法只接受一个参数。参数的类型是final类。在代码方面:

public class Utility {

   html" target="_blank">public static Optional<String> getName(Customer customer) {
       // method's body.
   }
}

public final class Customer {
   // class definition
}

因此,对于实用程序类,我创建了一个测试类UtilityTests,其中我为这个方法编写了测试,getName。单元测试框架是TestNG,使用的模拟库是Mockito。因此,典型测试具有以下结构:

public class UtilityTests {

   @Test
   public void getNameTest() {
     // Arrange
     Customer customerMock = Mockito.mock(Customer.class);
     Mockito.when(...).thenReturn(...);

     // Act
     Optional<String> name = Utility.getName(customerMock);

     // Assert
     Assert.assertTrue(...);
   }
}

有什么问题?

尽管测试在IntelliJ内部成功地在本地运行,但在Jenkins上失败(当我在远程分支中推送代码时,会触发构建,并在最后运行单元测试)。错误消息如下所示:

组织。莫基托。例外情况。基础MockitoException:无法模拟/间谍类com。帕卡吉纳姆。客户Mockito无法模拟/间谍,因为:-最终类

我试过什么?

为了找到解决办法,我找了一点,但没有成功。我在这里注意到,我不允许改变客户是最终类的事实。除此之外,如果可能的话,我希望完全不改变它的设计(例如,创建一个接口,它将保存我想要模拟的方法,并声明客户类实现了该接口,正如Jose在他的评论中指出的那样)。我尝试的是mockito决赛中提到的第二个选择。尽管这解决了问题,但它阻止了其他一些单元测试:(,这不能以任何明显的方式修复。

问题

所以我有两个问题:

  1. 首先,这怎么可能呢?测试不应该在本地和Jenkins都失败吗

提前感谢任何帮助。

共有3个答案

鲁羽
2023-03-14

确保使用相同的参数运行测试。检查您的intellij跑步配置是否与jenkins匹配。https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html.您可以尝试在本地计算机上使用与jenkins相同的参数(从终端)运行测试,如果测试失败,这意味着问题出在参数上

子车芷阳
2023-03-14

首先这怎么可能呢?测试不是应该在本地和詹金斯都失败吗?

这显然是一种环境细节。唯一的问题是——如何确定差异的原因。

我建议您查看org。莫基托。内部的util。MockUtil#typeMockabilityOf方法,并比较两种环境中实际使用的mockMaker以及原因。

如果mockMaker是相同的-比较加载的类IDE-客户端vsJenkins-客户端-它们在测试执行时间上有任何差异。

如何根据我上面提到的约束来解决这个问题?

以下代码是在OpenJDK 12和Mockito 2.28的假设下编写的。2,但我相信你可以将它调整到任何实际使用的版本。

public class UtilityTest {    
    @Rule
    public InlineMocksRule inlineMocksRule = new InlineMocksRule();

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testFinalClass() {
        // Given
        String testName = "Ainz Ooal Gown";
        Client client = Mockito.mock(Client.class);
        Mockito.when(client.getName()).thenReturn(testName);

        // When
        String name = Utility.getName(client).orElseThrow();

        // Then
        assertEquals(testName, name);
    }

    static final class Client {
        final String getName() {
            return "text";
        }
    }

    static final class Utility {
        static Optional<String> getName(Client client) {
            return Optional.ofNullable(client).map(Client::getName);
        }
    }    
}

使用单独的内联模拟规则:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class InlineMocksRule implements TestRule {
    private static Field MOCK_MAKER_FIELD;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
            VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);

            MOCK_MAKER_FIELD = MockUtil.class.getDeclaredField("mockMaker");
            MOCK_MAKER_FIELD.setAccessible(true);

            int mods = MOCK_MAKER_FIELD.getModifiers();
            if (Modifier.isFinal(mods)) {
                modifiers.set(MOCK_MAKER_FIELD, mods & ~Modifier.FINAL);
            }
        } catch (IllegalAccessException | NoSuchFieldException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Object oldMaker = MOCK_MAKER_FIELD.get(null);
                MOCK_MAKER_FIELD.set(null, Plugins.getPlugins().getInlineMockMaker());
                try {
                    base.evaluate();
                } finally {
                    MOCK_MAKER_FIELD.set(null, oldMaker);
                }
            }
        };
    }
}
高奇
2023-03-14

另一种方法是使用“方法到类”模式。

  1. 将方法从customer类移到另一个类中,比如CustomerSomething,例如CustomerFinances(或者它的职责是什么)
  2. 向客户添加构造函数
  3. 现在,您不需要模仿Customer,只需要CustomerSomething类!如果它没有外部依赖项,您可能也不需要模拟它

这里有一个关于这个主题的好博客:https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/

 类似资料:
  • 我正在将Powermock从我目前正在进行的项目中移除,因此我试图仅用Mockito(Mockito-Core-2.2.28)重写一些现有的unitary测试。 无法模拟/Spy类Com.ExternalPackagePath.ExternalClass Mockito无法模仿/监视,因为: 最终类 正如Mockito文档(https://github.com/Mockito/Mockito/wi

  • 我在一个maven项目中使用了mockito-core-2.7.21,它正按预期工作。 但是为了启用对final类的模拟,我在源文件夹下创建了一个文件并添加了文本。 按照https://www.baeldung.com/mockito-final以及Mock final class with mockito2,它应该允许我模拟final class,但当我运行junit测试时,我得到以下错误: J

  • 我在github示例中发现了如何使用standart Mockito创建final类(BluetoOthgatt.class)实例: 但从Mockito常见问题: Mockito的局限性是什么 需要java 1.5+ 无法模拟最终类 ... 我检查了它是来自标准android-sdk的BluetoothGatt,所以它看起来像模拟的最终类。现在我尝试build project,以确保该测试工作正常

  • 我无法使用mockito2模拟一个Kotlin final类。另外我还在用Robolectric。 这是我的测试代码: 请注意,我使用的是Mockito Version2,我使用的是依赖项,它自动启用模拟最终类的功能。

  • 我有一个测试,在詹金斯内部运行时总是失败。 我的项目包括Selenium webdriver、JAVA、Maven、TestNG、Jenkins和Allure(reports)。我有几个包含100个测试用例的测试套件,我通过3种不同的浏览器对它们进行迭代(这些测试使用TestNG并行运行)。它们都运行(使用maven命令行)并传入我的开发笔记本电脑,使用命令行时在测试服务器上运行。 我有两个关于J

  • 我有一个,有两个Test和,使用原始bean,而使用mockedBean:所以总是有一个测试失败。有没有办法只模拟我的bean为