我正在使用 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);
}
首先,我不会直接在测试方法中初始化和拆除模拟,而是使用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) {
...
}
}
如果您无法更改生产代码以通过反射重置字段,您还有另一种选择: 设置私有静态字段的值
我想我看到了类似的东西,但它是针对另一个测试类中的测试,该测试类似乎是并行执行的。因此,如果您有其他测试也进行类似的模拟,并且它们在 CI/CD 管道中并行运行,那么它可能不是测试类中影响其他测试的第一个测试,但也许另一个测试类中的一些其他测试也进行模拟并同时运行。请参阅https://github.com/jmockit/jmockit1/issues/325
不确定修复是什么,但我将尝试使用 Mockito 而不是 JMockit。