使用 jmockit 中的 MockUp 时,即使在拆解其他测试后,也会使用模拟方法

问题描述 投票:0回答:2

我正在使用 jmockit 版本 1.24 和 junit5,其中我模拟了单例类的公共方法,如下所示。

这是我的 Test.java:

@Test
void myTest() {
    MockUp mySingletonMock = new MockUp<MySingleton>() {
        @Mock
        public FileHeaders getHeader(String localFilePath) {
            return new FileHeaders(checksum, "", "", new Date());
        }
    };

    // Some assert statements 

    mySingletonMock.tearDown();
}

这是 Singleton.java:

public class MySingleton {
    private static MySingleton instance = new MySingleton();

    private MySingleton(){
        // Some initialization
    }

    public static MySingleton getInstance(){
        return instance;
    }

    public FileHeaders getHeader(String localFilePath) {
        ...
    }
}

我面临上述方法的一个问题,即

myTest
完成执行后执行的所有测试都会失败,因为它们仍然看到模拟的
getHeader
方法,而不是 MySingleton 类中的原始方法(我已经验证这确实是使用调试语句的情况)。

如何防止在其他测试中看到此模拟版本的

getHeader
方法? (最好不要改变jmockit的版本)。

所有这一切的奇怪部分是,使用 Maven 在我的系统上本地运行测试没有任何问题。但在 teamcity 上运行时失败。

如有任何帮助,我们将不胜感激。谢谢你。

编辑: 我尝试过的事情:

  • 我尝试将

    $clinit()
    方法添加到 MockUp 中。但没有运气。

  • 我在测试结束时通过反射将单例实例重置为新实例,如下所示。这也没有解决问题。

void resetMySingletonInstance() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
    Constructor<?>[] constructors = MySingleton.class.getDeclaredConstructors();
    Constructor theConstructor = constructors[0];
    theConstructor.setAccessible(true);

    // Verified that this gives a new instance
    MySingleton instanceMySingleton = (MySingleton) theConstructor.newInstance();
    Field ourInstanceField = MySingleton.class.getDeclaredField("ourInstance");
    ourInstanceField.setAccessible(true);
    ourInstanceField.set(null, instanceMySingleton);
}
java static singleton junit5 jmockit
2个回答
0
投票

首先,我不会直接在测试方法中初始化和拆除模拟,而是使用setup()和tearDown()方法(@Before和@After注释)。

private List<MockUp<?>> mockUps = new ArrayList<>();
    
public void addMockUp(MockUp<?> mockUp) {
    mockUps.add(mockUp);
}

@Before
public void setup() throws Exception {
    addMockUp(new MockUp<MySingleton>() {
        @Mock
        public FileHeaders getHeader(String localFilePath) {
            return new FileHeaders(checksum, "", "", new Date());
        }
    });
    
    // other mocks ?
}

@Test
void myTest() {     
    // Some assert statements on MySingleton.getInstance()
}

@After
public void tearDown() {        
    mockUps.stream().forEach(mock->mock.tearDown());
}

但是您测试中的问题是 MySingleton 是用模拟对象初始化的。如果所有测试都在同一个虚拟机中执行,则该模拟对象将在后续测试中重用。 我不知道为什么你在本地没有这个问题,而只在你的构建服务器上有这个问题,也许是虚拟机分叉的配置:https://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-选项和并行执行.html

要解决这个问题,您需要找到一种方法在每次测试后重新初始化单例。 例如,一种解决方案是更改 MySingleton,以便能够在测试后重置实例。如果 MySingleton 不是同时访问,您可以使用延迟加载,并在tearDown 方法中重置它。

公共类 MySingleton { 私有静态 MySingleton 实例;

private MySingleton(){
    // Some initialization
}

public static MySingleton getInstance(){
    if(instance == null) {
        instance = new MySingleton();
    }
    return instance;
}

public static void reset() {
    instance = null;
}

public FileHeaders getHeader(String localFilePath) {
    ...
}

}

如果您无法更改生产代码以通过反射重置字段,您还有另一种选择: 设置私有静态字段的值


0
投票

我想我看到了类似的东西,但它是针对另一个测试类中的测试,该测试类似乎是并行执行的。因此,如果您有其他测试也进行类似的模拟,并且它们在 CI/CD 管道中并行运行,那么它可能不是测试类中影响其他测试的第一个测试,但也许另一个测试类中的一些其他测试也进行模拟并同时运行。请参阅https://github.com/jmockit/jmockit1/issues/325

不确定修复是什么,但我将尝试使用 Mockito 而不是 JMockit。

© www.soinside.com 2019 - 2024. All rights reserved.