我有以下设置:
在测试期间需要模拟的示例类:
@Component
class MyConfig
{
public String getConfig()
{
return "RealValue";
}
}
定义单个方法的接口,有两个实现:
interface MyInterface
{
String myMethod();
}
class MyImpl1 implements MyInterface
{
private final MyInterface delegate;
private final MyConfig config;
public MyImpl1(final MyInterface delegate, final MyConfig config)
{
this.delegate = delegate;
this.config = config;
}
@Override
public String myMethod()
{
return this.getClass().getSimpleName() + ": " + config.getConfig() + ", " + delegate.myMethod() + ";";
}
}
class MyImpl2 implements MyInterface
{
private final MyConfig config;
public MyImpl2(final MyConfig config)
{
this.config = config;
}
@Override
public String myMethod()
{
return this.getClass().getSimpleName() + ": " + config.getConfig();
}
}
使用注入MyInterface
的弹簧bean的MyConfig
对象准备类型为MyFactory
的bean的工厂类:
@Component
class MyFactory
{
@Autowired
private MyConfig config;
// Factory method to create the bean.
@Bean(name = "myInterface")
protected MyInterface myInterface()
{
final MyImpl2 myImpl2 = new MyImpl2(config);
final MyImpl1 myImpl1 = new MyImpl1(myImpl2, config);
return myImpl1;
}
// A simple getter that prepares MyInterface on the fly.
// This is just to demonstrate that getter picks the mock where as
// the factory bean doesn't
public MyInterface getInterface()
{
final MyImpl2 myImpl2 = new MyImpl2(config);
final MyImpl1 myImpl1 = new MyImpl1(myImpl2, config);
return myImpl1;
}
}
一个简单的测试用例,用于检查MyConfig
的模拟版本是否进入使用@Bean
创建的bean:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:application-context.xml"
})
public class SpringBeanMockExampleTest
{
@Mock
private MyConfig config;
@InjectMocks
@Autowired
protected MyFactory factory;
@Resource
private MyInterface myInterface;
@Before
public void setupMocks()
{
MockitoAnnotations.initMocks(this);
}
/**
* Fails as the return value is "MyImpl1: RealValue, MyImpl2: RealValue;"
*/
@Test
public void testBean()
{
Mockito.when(config.getConfig()).thenReturn("MockValue");
Assert.assertEquals("MyImpl1: MockValue, MyImpl2: MockValue;", myInterface.myMethod());
}
/**
* Assertion passes here.
*/
@Test
public void testGetter()
{
Mockito.when(config.getConfig()).thenReturn("MockValue");
Assert.assertEquals("MyImpl1: MockValue, MyImpl2: MockValue;", factory.getInterface().myMethod());
}
}
我希望testBean
方法也可以通过,但显然模拟不会被注入到MyFactory
中创建的工厂bean中。
在工厂bean创建步骤完成后,模拟似乎正在替换实际的bean。因此,工厂bean中的引用不会使用mock更新。
如何解决此问题,以便testBean
按预期工作?
答案 0 :(得分:1)
这不会奏效。
首先初始化您的Spring上下文。然后执行TestExecutionListener
来处理测试中的依赖注入(例如@Autowired
)。
然后,在每个测试运行之前,您的@Before
方法将在SpringBeanMockExampleTest
的测试实例中初始化Mockito模拟,从而有效地覆盖自动连接的Spring依赖项。
为什么呢? Mockito为使用@InjectMocks
,@Mock
,@Spy
和@Captor
注释的所有属性创建了一个新实例。
一种可能的解决方案是在工厂中手动设置模拟配置,而不是使用@InjectMocks
,覆盖Spring配置bean。
@Before
public void setupMocks(){
Config config = mock(Config.class);
factory.setConfig(config);
}
请注意,将模拟与(Spring)集成测试相结合是不良做法,因为模拟只能在单元测试中完成。
更好的设置是使用配置文件在Spring上下文中设置单独的Config
bean,例如:
@Profile("testing")
@Component
public class TestConfig implements Config {
public String getConfig(){
return "testValue";
}
}