Java Junit Jupiter:如何更改缓存在测试类外部列表中的mockito模拟行为?
在我的场景中,我有一个简单的类,其方法返回字符串:Java Junit Jupiter:如何更改缓存在测试类外部列表中的mockito模拟行为?,java,unit-testing,arraylist,mockito,junit5,Java,Unit Testing,Arraylist,Mockito,Junit5,在我的场景中,我有一个简单的类,其方法返回字符串: public class Foo { public String bar() { return "1"; } } 对于简化,列表将存储Foo的实例(在现实项目中,这是某种工厂/缓存组合): 第一个测试方法firstTest成功完成,foo返回“2”,并且foo现在存储在缓存列表中。 第二种测试方法secondTest因“2不等于3”而失败,因为 将更改foo的行为,但对缓存中的模拟对象没有影
public class Foo {
public String bar() {
return "1";
}
}
对于简化,列表将存储Foo
的实例(在现实项目中,这是某种工厂/缓存组合):
第一个测试方法firstTest
成功完成,foo
返回“2”,并且foo
现在存储在缓存列表中。
第二种测试方法secondTest
因“2不等于3”而失败,因为
将更改foo
的行为,但对缓存中的模拟对象没有影响,该对象将用于调用FooCache#getOrCreateFoo
为什么会出现这种情况?在将模拟对象存储在测试类之外的列表中之后,有没有任何方法可以更改模拟对象的行为?有多种方法可以解决此问题
重构静态类并包含clearCache
方法
公共类缓存{
私有静态列表缓存=新的ArrayList();
公共静态Foo getOrCreateFoo(Foo-Foo){
if(cache.isEmpty()){
cache.add(foo);
}
返回cache.get(0);
}
公共静态void clearFooCache(){
cache.clear();
}
}
在你的测试中
@BeforeEach
public void setUp() {
FooCache.clearCache();
}
使用反射访问FooCache\cache
@beforeach
公共作废设置(){
字段缓存=FooCache.class.getDeclaredField(“缓存”);
cache.setAccessible(true);
List listofoos=(List)cache.get(FooCache.class);
listofoos.clear();
}
在每个测试中使用Mockito的mockStatic
实用程序
try(MockedStatic theMock=Mockito.mockStatic(YearMonth.class,Mockito.CALLS\u REAL\u方法)){
doReturn(anyValue).when(theMock.getOrCreateFoo(any());
}
仅解释一下这里发生了什么:
在firstTest()启动之前,将创建一个新的Foo mock,稍后将返回“2”。这将添加到静态缓存中。断言是正确的
在启动secondTest()之前,将创建另一个新的Foo mock,稍后将返回“3”。这将添加到静态缓存中。由于代码是静态的,所以仍然包含第一个mock,这使得断言失败
经验教训:
静态代码是有害的,尤其是静态非常量类属性。即使工厂也应该以非静态的方式创建/使用。这是一种反模式
解决方案:
从代码中删除所有静态修饰符
在每次测试运行时实例化FooCache:
public-class-FooTest{
@嘲弄
私人富福;
//测试中的系统(将在每次测试前实例化)
//这就是您实际测试的对象。
private FooCache sut=new FooCache();
@试验
void firstTest(){
//安排
when(foo.bar())。然后返回(“2”);
//表演
Foo uut=sut.getOrCreateFoo(Foo);
字符串实际值=uut.bar();
//断言
资产质量(“2”,实际);
}
@试验
void secondTest(){
//安排
when(foo.bar())。然后返回(“3”);
//表演
Foo uut=sut.getOrCreateFoo(Foo);
字符串实际值=uut.bar();
//断言
assertEquals(“3”,实际);//失败,3不等于2
}
}
刚刚找到了原因:正如中及其第一和第二条注释所述,“JUnit设计者希望测试方法之间的测试隔离,因此它创建了一个新的测试类实例来运行每个测试方法。”
因此,来自第一个方法的foo存储在列表中,随着第二个测试方法的开始,另一个foo已经创建!以下任何更改将不再影响第一个方法foo
这不仅是我的szenario中的一个问题,而且在处理单例时可以在setUp()方法上添加@beforeach注释,在这里可以实际为每个测试创建新的模拟,使用foo=Mockito.mock(foo.class)重新初始化;或者用Mockito.reset(foo)重置;不会解决此问题,因为无法访问列表中的foo对象。谢谢,我知道这些解决此问题的选项,但我对发生这种情况的原因更感兴趣。请参阅我的答案,了解发生了什么以及为什么保留静态代码不是解决方案。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class FooTest {
@Mock
private Foo foo;
@Test
void firstTest() {
// Arrange
when(foo.bar()).thenReturn("2");
// Act
Foo uut = FooCache.getOrCreateFoo(foo);
String actual = uut.bar();
// Assert
assertEquals("2", actual);
}
@Test
void secondTest() {
// Arrange
when(foo.bar()).thenReturn("3"); // <--- HAS NO EFFECT ON CACHED FOO
// Act
Foo uut = FooCache.getOrCreateFoo(foo);
String actual = uut.bar();
// Assert
assertEquals("3", actual); // fails with 3 not equals 2
}
}
when(foo.bar()).thenReturn("3")
@BeforeEach
public void setUp() {
FooCache.clearCache();
}