将Mockito mock注入通过工厂方法返回的bean中



我有以下设置:

测试期间需要模拟的示例类:

@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上下文中设置一个单独的Configbean,例如:

@Profile("testing")
@Component
public class TestConfig implements Config {
    public String getConfig(){
        return "testValue";    
    }
}

相关内容

  • 没有找到相关文章