我有以下设置:
测试期间需要模拟的示例类:
@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();
}
}
一个工厂类,使用MyConfig
对象来准备类型为MyInterface
的bean,该对象是注入到MyFactory
:中的spring 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
方法也能通过,但很明显,mock并没有被注入到MyFactory
中创建的工厂bean中。
在工厂bean创建步骤完成后,mock似乎正在替换实际的bean。因此,工厂bean中的引用不会使用mock进行更新。
如何修复此问题,使testBean
按预期工作?
这行不通。
首先初始化您的Spring上下文。然后执行TestExecutionListener
,处理测试中的依赖项注入(例如@Autowired
)。
然后,在运行每个测试之前,@Before
方法将在SpringBeanMockExampleTest
的测试实例中初始化Mockito mock,从而有效地覆盖自动连接的Spring依赖关系。
为什么?Mockito为所有用@InjectMocks
、@Mock
、@Spy
和@Captor
注释的属性创建一个新实例。
一个可能的解决方案是在工厂中手动设置模拟配置,而不是使用@InjectMocks
,覆盖Spring配置bean。
@Before
public void setupMocks(){
Config config = mock(Config.class);
factory.setConfig(config);
}
请注意,将mock与(Spring)集成测试相结合是一种糟糕的做法,因为mock只能在单元测试中进行。
更好的设置是使用概要文件在Spring上下文中设置一个单独的Config
bean,例如:
@Profile("testing")
@Component
public class TestConfig implements Config {
public String getConfig(){
return "testValue";
}
}